summaryrefslogtreecommitdiffhomepage
path: root/tools/checkescape/checkescape.go
diff options
context:
space:
mode:
Diffstat (limited to 'tools/checkescape/checkescape.go')
-rw-r--r--tools/checkescape/checkescape.go918
1 files changed, 0 insertions, 918 deletions
diff --git a/tools/checkescape/checkescape.go b/tools/checkescape/checkescape.go
deleted file mode 100644
index e5a7e23c7..000000000
--- a/tools/checkescape/checkescape.go
+++ /dev/null
@@ -1,918 +0,0 @@
-// Copyright 2020 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package checkescape allows recursive escape analysis for hot paths.
-//
-// The analysis tracks multiple types of escapes, in two categories. First,
-// 'hard' escapes are explicit allocations. Second, 'soft' escapes are
-// interface dispatches or dynamic function dispatches; these don't necessarily
-// escape but they *may* escape. The analysis is capable of making assertions
-// recursively: soft escapes cannot be analyzed in this way, and therefore
-// count as escapes for recursive purposes.
-//
-// The different types of escapes are as follows, with the category in
-// parentheses:
-//
-// heap: A direct allocation is made on the heap (hard).
-// builtin: A call is made to a built-in allocation function (hard).
-// stack: A stack split as part of a function preamble (soft).
-// interface: A call is made via an interface whicy *may* escape (soft).
-// dynamic: A dynamic function is dispatched which *may* escape (soft).
-//
-// To the use the package, annotate a function-level comment with either the
-// line "// +checkescape" or "// +checkescape:OPTION[,OPTION]". In the second
-// case, the OPTION field is either a type above, or one of:
-//
-// local: Escape analysis is limited to local hard escapes only.
-// all: All the escapes are included.
-// hard: All hard escapes are included.
-//
-// If the "// +checkescape" annotation is provided, this is equivalent to
-// provided the local and hard options.
-//
-// Some examples of this syntax are:
-//
-// +checkescape:all - Analyzes for all escapes in this function and all calls.
-// +checkescape:local - Analyzes only for default local hard escapes.
-// +checkescape:heap - Only analyzes for heap escapes.
-// +checkescape:interface,dynamic - Only checks for dynamic calls and interface calls.
-// +checkescape - Does the same as +checkescape:local,hard.
-//
-// Note that all of the above can be inverted by using +mustescape. The
-// +checkescape keyword will ensure failure if the class of escape occurs,
-// whereas +mustescape will fail if the given class of escape does not occur.
-//
-// Local exemptions can be made by a comment of the form "// escapes: reason."
-// This must appear on the line of the escape and will also apply to callers of
-// the function as well (for non-local escape analysis).
-package checkescape
-
-import (
- "bufio"
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "io"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/buildssa"
- "golang.org/x/tools/go/ssa"
-)
-
-const (
- // magic is the magic annotation.
- magic = "// +checkescape"
-
- // magicParams is the magic annotation with specific parameters.
- magicParams = magic + ":"
-
- // testMagic is the test magic annotation (parameters required).
- testMagic = "// +mustescape:"
-
- // exempt is the exemption annotation.
- exempt = "// escapes"
-)
-
-var (
- // Binary is the binary under analysis.
- //
- // See Reader, below.
- binary = flag.String("binary", "", "binary under analysis")
-
- // Reader is the input stream.
- //
- // This may be set instead of Binary.
- Reader io.Reader
-
- // objdumpTool is the tool used to dump a binary.
- objdumpTool = flag.String("objdump_tool", "", "tool used to dump a binary")
-)
-
-// EscapeReason is an escape reason.
-//
-// This is a simple enum.
-type EscapeReason int
-
-const (
- allocation EscapeReason = iota
- builtin
- interfaceInvoke
- dynamicCall
- stackSplit
- unknownPackage
- reasonCount // Count for below.
-)
-
-// String returns the string for the EscapeReason.
-//
-// Note that this also implicitly defines the reverse string -> EscapeReason
-// mapping, which is the word before the colon (computed below).
-func (e EscapeReason) String() string {
- switch e {
- case interfaceInvoke:
- return "interface: call to potentially allocating function"
- case unknownPackage:
- return "unknown: no package information available"
- case allocation:
- return "heap: explicit allocation"
- case builtin:
- return "builtin: call to potentially allocating builtin"
- case dynamicCall:
- return "dynamic: call to potentially allocating function"
- case stackSplit:
- return "stack: possible split on function entry"
- default:
- panic(fmt.Sprintf("unknown reason: %d", e))
- }
-}
-
-var hardReasons = []EscapeReason{
- allocation,
- builtin,
-}
-
-var softReasons = []EscapeReason{
- interfaceInvoke,
- unknownPackage,
- dynamicCall,
- stackSplit,
-}
-
-var allReasons = append(hardReasons, softReasons...)
-
-var escapeTypes = func() map[string]EscapeReason {
- result := make(map[string]EscapeReason)
- for _, r := range allReasons {
- parts := strings.Split(r.String(), ":")
- result[parts[0]] = r // Key before ':'.
- }
- return result
-}()
-
-// escapingBuiltins are builtins known to escape.
-//
-// These are lowered at an earlier stage of compilation to explicit function
-// calls, but are not available for recursive analysis.
-var escapingBuiltins = []string{
- "append",
- "makemap",
- "newobject",
- "mallocgc",
-}
-
-// packageEscapeFacts is the set of all functions in a package, and whether or
-// not they recursively pass escape analysis.
-//
-// All the type names for receivers are encoded in the full key. The key
-// represents the fully qualified package and type name used at link time.
-//
-// Note that each Escapes object is a summary. Local findings may be reported
-// using more detailed information.
-type packageEscapeFacts struct {
- Funcs map[string]Escapes
-}
-
-// AFact implements analysis.Fact.AFact.
-func (*packageEscapeFacts) AFact() {}
-
-// Analyzer includes specific results.
-var Analyzer = &analysis.Analyzer{
- Name: "checkescape",
- Doc: "escape analysis checks based on +checkescape annotations",
- Run: runSelectEscapes,
- Requires: []*analysis.Analyzer{buildssa.Analyzer},
- FactTypes: []analysis.Fact{(*packageEscapeFacts)(nil)},
-}
-
-// EscapeAnalyzer includes all local escape results.
-var EscapeAnalyzer = &analysis.Analyzer{
- Name: "checkescape",
- Doc: "complete local escape analysis results (requires Analyzer facts)",
- Run: runAllEscapes,
- Requires: []*analysis.Analyzer{buildssa.Analyzer},
-}
-
-// LinePosition is a low-resolution token.Position.
-//
-// This is used to match against possible exemptions placed in the source.
-type LinePosition struct {
- Filename string
- Line int
-}
-
-// String implements fmt.Stringer.String.
-func (e LinePosition) String() string {
- return fmt.Sprintf("%s:%d", e.Filename, e.Line)
-}
-
-// Simplified returns the simplified name.
-func (e LinePosition) Simplified() string {
- return fmt.Sprintf("%s:%d", filepath.Base(e.Filename), e.Line)
-}
-
-// CallSite is a single call site.
-//
-// These can be chained.
-type CallSite struct {
- LocalPos token.Pos
- Resolved LinePosition
-}
-
-// IsValid indicates whether the CallSite is valid or not.
-func (cs *CallSite) IsValid() bool {
- return cs.LocalPos.IsValid()
-}
-
-// Escapes is a collection of escapes.
-//
-// We record at most one escape for each reason, but record the number of
-// escapes that were omitted.
-//
-// This object should be used to summarize all escapes for a single line (local
-// analysis) or a single function (package facts).
-//
-// All fields are exported for gob.
-type Escapes struct {
- CallSites [reasonCount][]CallSite
- Details [reasonCount]string
- Omitted [reasonCount]int
-}
-
-// add is called by Add and Merge.
-func (es *Escapes) add(r EscapeReason, detail string, omitted int, callSites ...CallSite) {
- if es.CallSites[r] != nil {
- // We will either be replacing the current escape or dropping
- // the added one. Either way, we increment omitted by the
- // appropriate amount.
- es.Omitted[r]++
- // If the callSites in the other is only a single element, then
- // we will universally favor this. This provides the cleanest
- // set of escapes to summarize, and more importantly: if there
- if len(es.CallSites) == 1 || len(callSites) != 1 {
- return
- }
- }
- es.Details[r] = detail
- es.CallSites[r] = callSites
- es.Omitted[r] += omitted
-}
-
-// Add adds a single escape.
-func (es *Escapes) Add(r EscapeReason, detail string, callSites ...CallSite) {
- es.add(r, detail, 0, callSites...)
-}
-
-// IsEmpty returns true iff this Escapes is empty.
-func (es *Escapes) IsEmpty() bool {
- for _, cs := range es.CallSites {
- if cs != nil {
- return false
- }
- }
- return true
-}
-
-// Filter filters out all escapes except those matches the given reasons.
-//
-// If local is set, then non-local escapes will also be filtered.
-func (es *Escapes) Filter(reasons []EscapeReason, local bool) {
-FilterReasons:
- for r := EscapeReason(0); r < reasonCount; r++ {
- for i := 0; i < len(reasons); i++ {
- if r == reasons[i] {
- continue FilterReasons
- }
- }
- // Zap this reason.
- es.CallSites[r] = nil
- es.Details[r] = ""
- es.Omitted[r] = 0
- }
- if !local {
- return
- }
- for r := EscapeReason(0); r < reasonCount; r++ {
- // Is does meet our local requirement?
- if len(es.CallSites[r]) > 1 {
- es.CallSites[r] = nil
- es.Details[r] = ""
- es.Omitted[r] = 0
- }
- }
-}
-
-// MergeWithCall merges these escapes with another.
-//
-// If callSite is nil, no call is added.
-func (es *Escapes) MergeWithCall(other Escapes, callSite CallSite) {
- for r := EscapeReason(0); r < reasonCount; r++ {
- if other.CallSites[r] != nil {
- // Construct our new call chain.
- newCallSites := other.CallSites[r]
- if callSite.IsValid() {
- newCallSites = append([]CallSite{callSite}, newCallSites...)
- }
- // Add (potentially replacing) the underlying escape.
- es.add(r, other.Details[r], other.Omitted[r], newCallSites...)
- }
- }
-}
-
-// Reportf will call Reportf for each class of escapes.
-func (es *Escapes) Reportf(pass *analysis.Pass) {
- var b bytes.Buffer // Reused for all escapes.
- for r := EscapeReason(0); r < reasonCount; r++ {
- if es.CallSites[r] == nil {
- continue
- }
- b.Reset()
- fmt.Fprintf(&b, "%s ", r.String())
- if es.Omitted[r] > 0 {
- fmt.Fprintf(&b, "(%d omitted) ", es.Omitted[r])
- }
- for _, cs := range es.CallSites[r][1:] {
- fmt.Fprintf(&b, "→ %s ", cs.Resolved.String())
- }
- fmt.Fprintf(&b, "→ %s", es.Details[r])
- pass.Reportf(es.CallSites[r][0].LocalPos, b.String())
- }
-}
-
-// MergeAll merges a sequence of escapes.
-func MergeAll(others []Escapes) (es Escapes) {
- for _, other := range others {
- es.MergeWithCall(other, CallSite{})
- }
- return
-}
-
-// loadObjdump reads the objdump output.
-//
-// This records if there is a call any function for every source line. It is
-// used only to remove false positives for escape analysis. The call will be
-// elided if escape analysis is able to put the object on the heap exclusively.
-//
-// Note that the map uses <basename.go>:<line> because that is all that is
-// provided in the objdump format. Since this is all local, it is sufficient.
-func loadObjdump() (map[string][]string, error) {
- var (
- args []string
- stdin io.Reader
- )
- if *binary != "" {
- args = append(args, *binary)
- } else if Reader != nil {
- stdin = Reader
- } else {
- // We have no input stream or binary.
- return nil, fmt.Errorf("no binary or reader provided")
- }
-
- // Construct our command.
- cmd := exec.Command(*objdumpTool, args...)
- cmd.Stdin = stdin
- cmd.Stderr = os.Stderr
- out, err := cmd.StdoutPipe()
- if err != nil {
- return nil, err
- }
- if err := cmd.Start(); err != nil {
- return nil, err
- }
-
- // Identify calls by address or name. Note that this is also
- // constructed dynamically below, as we encounted the addresses.
- // This is because some of the functions (duffzero) may have
- // jump targets in the middle of the function itself.
- funcsAllowed := map[string]struct{}{
- "runtime.duffzero": struct{}{},
- "runtime.duffcopy": struct{}{},
- "runtime.racefuncenter": struct{}{},
- "runtime.gcWriteBarrier": struct{}{},
- "runtime.retpolineAX": struct{}{},
- "runtime.retpolineBP": struct{}{},
- "runtime.retpolineBX": struct{}{},
- "runtime.retpolineCX": struct{}{},
- "runtime.retpolineDI": struct{}{},
- "runtime.retpolineDX": struct{}{},
- "runtime.retpolineR10": struct{}{},
- "runtime.retpolineR11": struct{}{},
- "runtime.retpolineR12": struct{}{},
- "runtime.retpolineR13": struct{}{},
- "runtime.retpolineR14": struct{}{},
- "runtime.retpolineR15": struct{}{},
- "runtime.retpolineR8": struct{}{},
- "runtime.retpolineR9": struct{}{},
- "runtime.retpolineSI": struct{}{},
- "runtime.stackcheck": struct{}{},
- "runtime.settls": struct{}{},
- }
- addrsAllowed := make(map[string]struct{})
-
- // Build the map.
- nextFunc := "" // For funcsAllowed.
- m := make(map[string][]string)
- r := bufio.NewReader(out)
-NextLine:
- for {
- line, err := r.ReadString('\n')
- if err != nil && err != io.EOF {
- return nil, err
- }
- fields := strings.Fields(line)
-
- // Is this an "allowed" function definition?
- if len(fields) >= 2 && fields[0] == "TEXT" {
- nextFunc = strings.TrimSuffix(fields[1], "(SB)")
- if _, ok := funcsAllowed[nextFunc]; !ok {
- nextFunc = "" // Don't record addresses.
- }
- }
- if nextFunc != "" && len(fields) > 2 {
- // Save the given address (in hex form, as it appears).
- addrsAllowed[fields[1]] = struct{}{}
- }
-
- // We recognize lines corresponding to actual code (not the
- // symbol name or other metadata) and annotate them if they
- // correspond to an explicit CALL instruction. We assume that
- // the lack of a CALL for a given line is evidence that escape
- // analysis has eliminated an allocation.
- //
- // Lines look like this (including the first space):
- // gohacks_unsafe.go:33 0xa39 488b442408 MOVQ 0x8(SP), AX
- if len(fields) >= 5 && line[0] == ' ' {
- if !strings.Contains(fields[3], "CALL") {
- continue
- }
- site := fields[0]
- target := strings.TrimSuffix(fields[4], "(SB)")
-
- // Ignore strings containing allowed functions.
- if _, ok := funcsAllowed[target]; ok {
- continue
- }
- if _, ok := addrsAllowed[target]; ok {
- continue
- }
- if len(fields) > 5 {
- // This may be a future relocation. Some
- // objdump versions describe this differently.
- // If it contains any of the functions allowed
- // above as a string, we let it go.
- softTarget := strings.Join(fields[5:], " ")
- for name := range funcsAllowed {
- if strings.Contains(softTarget, name) {
- continue NextLine
- }
- }
- }
-
- // Does this exist already?
- existing, ok := m[site]
- if !ok {
- existing = make([]string, 0, 1)
- }
- for _, other := range existing {
- if target == other {
- continue NextLine
- }
- }
- existing = append(existing, target)
- m[site] = existing // Update.
- }
- if err == io.EOF {
- break
- }
- }
-
- // Zap any accidental false positives.
- final := make(map[string][]string)
- for site, calls := range m {
- filteredCalls := make([]string, 0, len(calls))
- for _, call := range calls {
- if _, ok := addrsAllowed[call]; ok {
- continue // Omit this call.
- }
- filteredCalls = append(filteredCalls, call)
- }
- final[site] = filteredCalls
- }
-
- // Wait for the dump to finish.
- if err := cmd.Wait(); err != nil {
- return nil, err
- }
-
- return final, nil
-}
-
-// poser is a type that implements Pos.
-type poser interface {
- Pos() token.Pos
-}
-
-// runSelectEscapes runs with only select escapes.
-func runSelectEscapes(pass *analysis.Pass) (interface{}, error) {
- return run(pass, false)
-}
-
-// runAllEscapes runs with all escapes included.
-func runAllEscapes(pass *analysis.Pass) (interface{}, error) {
- return run(pass, true)
-}
-
-// findReasons extracts reasons from the function.
-func findReasons(pass *analysis.Pass, fdecl *ast.FuncDecl) ([]EscapeReason, bool, map[EscapeReason]bool) {
- // Is there a comment?
- if fdecl.Doc == nil {
- return nil, false, nil
- }
- var (
- reasons []EscapeReason
- local bool
- testReasons = make(map[EscapeReason]bool) // reason -> local?
- )
- // Scan all lines.
- found := false
- for _, c := range fdecl.Doc.List {
- // Does the comment contain a +checkescape line?
- if !strings.HasPrefix(c.Text, magic) && !strings.HasPrefix(c.Text, testMagic) {
- continue
- }
- if c.Text == magic {
- // Default: hard reasons, local only.
- reasons = hardReasons
- local = true
- } else if strings.HasPrefix(c.Text, magicParams) {
- // Extract specific reasons.
- types := strings.Split(c.Text[len(magicParams):], ",")
- found = true // For below.
- for i := 0; i < len(types); i++ {
- if types[i] == "local" {
- // Limit search to local escapes.
- local = true
- } else if types[i] == "all" {
- // Append all reasons.
- reasons = append(reasons, allReasons...)
- } else if types[i] == "hard" {
- // Append all hard reasons.
- reasons = append(reasons, hardReasons...)
- } else {
- r, ok := escapeTypes[types[i]]
- if !ok {
- // This is not a valid escape reason.
- pass.Reportf(fdecl.Pos(), "unknown reason: %v", types[i])
- continue
- }
- reasons = append(reasons, r)
- }
- }
- } else if strings.HasPrefix(c.Text, testMagic) {
- types := strings.Split(c.Text[len(testMagic):], ",")
- local := false
- for i := 0; i < len(types); i++ {
- if types[i] == "local" {
- local = true
- } else {
- r, ok := escapeTypes[types[i]]
- if !ok {
- // This is not a valid escape reason.
- pass.Reportf(fdecl.Pos(), "unknown reason: %v", types[i])
- continue
- }
- if v, ok := testReasons[r]; ok && v {
- // Already registered as local.
- continue
- }
- testReasons[r] = local
- }
- }
- }
- }
- if len(reasons) == 0 && found {
- // A magic annotation was provided, but no reasons.
- pass.Reportf(fdecl.Pos(), "no reasons provided")
- }
- return reasons, local, testReasons
-}
-
-// run performs the analysis.
-func run(pass *analysis.Pass, localEscapes bool) (interface{}, error) {
- calls, err := loadObjdump()
- if err != nil {
- // Note that if this analysis fails, then we don't actually
- // fail the analyzer itself. We simply report every possible
- // escape. In most cases this will work just fine.
- log.Printf("WARNING: unable to load objdump: %v", err)
- }
- allEscapes := make(map[string][]Escapes)
- mergedEscapes := make(map[string]Escapes)
- linePosition := func(inst, parent poser) LinePosition {
- p := pass.Fset.Position(inst.Pos())
- if (p.Filename == "" || p.Line == 0) && parent != nil {
- p = pass.Fset.Position(parent.Pos())
- }
- return LinePosition{
- Filename: p.Filename,
- Line: p.Line,
- }
- }
- callSite := func(inst ssa.Instruction) CallSite {
- return CallSite{
- LocalPos: inst.Pos(),
- Resolved: linePosition(inst, inst.Parent()),
- }
- }
- hasCall := func(inst poser) (string, bool) {
- p := linePosition(inst, nil)
- if calls == nil {
- // See above: we don't have access to the binary
- // itself, so need to include every possible call.
- return "(possible)", true
- }
- s, ok := calls[p.Simplified()]
- if !ok {
- return "", false
- }
- // Join all calls together.
- return strings.Join(s, " or "), true
- }
- state := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
-
- // Build the exception list.
- exemptions := make(map[LinePosition]string)
- for _, f := range pass.Files {
- for _, cg := range f.Comments {
- for _, c := range cg.List {
- p := pass.Fset.Position(c.Slash)
- if strings.HasPrefix(strings.ToLower(c.Text), exempt) {
- exemptions[LinePosition{
- Filename: p.Filename,
- Line: p.Line,
- }] = c.Text[len(exempt):]
- }
- }
- }
- }
-
- var loadFunc func(*ssa.Function) Escapes // Used below.
- analyzeInstruction := func(inst ssa.Instruction) (es Escapes) {
- cs := callSite(inst)
- if _, ok := exemptions[cs.Resolved]; ok {
- return // No escape.
- }
- switch x := inst.(type) {
- case *ssa.Call:
- if x.Call.IsInvoke() {
- // This is an interface dispatch. There is no
- // way to know if this is actually escaping or
- // not, since we don't know the underlying
- // type.
- call, _ := hasCall(inst)
- es.Add(interfaceInvoke, call, cs)
- return
- }
- switch x := x.Call.Value.(type) {
- case *ssa.Function:
- if x.Pkg == nil {
- // Can't resolve the package.
- es.Add(unknownPackage, "no package", cs)
- return
- }
-
- // Is this a local function? If yes, call the
- // function to load the local function. The
- // local escapes are the escapes found in the
- // local function.
- if x.Pkg.Pkg == pass.Pkg {
- es.MergeWithCall(loadFunc(x), cs)
- return
- }
-
- // Recursively collect information from
- // the other analyzers.
- var imp packageEscapeFacts
- if !pass.ImportPackageFact(x.Pkg.Pkg, &imp) {
- // Unable to import the dependency; we must
- // declare these as escaping.
- es.Add(unknownPackage, "no analysis", cs)
- return
- }
-
- // The escapes of this instruction are the
- // escapes of the called function directly.
- // Note that this may record many escapes.
- es.MergeWithCall(imp.Funcs[x.RelString(x.Pkg.Pkg)], cs)
- return
- case *ssa.Builtin:
- // Ignore elided escapes.
- if _, has := hasCall(inst); !has {
- return
- }
-
- // Check if the builtin is escaping.
- for _, name := range escapingBuiltins {
- if x.Name() == name {
- es.Add(builtin, name, cs)
- return
- }
- }
- default:
- // All dynamic calls are counted as soft
- // escapes. They are similar to interface
- // dispatches. We cannot actually look up what
- // this refers to using static analysis alone.
- call, _ := hasCall(inst)
- es.Add(dynamicCall, call, cs)
- }
- case *ssa.Alloc:
- // Ignore non-heap allocations.
- if !x.Heap {
- return
- }
-
- // Ignore elided escapes.
- call, has := hasCall(inst)
- if !has {
- return
- }
-
- // This is a real heap allocation.
- es.Add(allocation, call, cs)
- case *ssa.MakeMap:
- es.Add(builtin, "makemap", cs)
- case *ssa.MakeSlice:
- es.Add(builtin, "makeslice", cs)
- case *ssa.MakeClosure:
- es.Add(builtin, "makeclosure", cs)
- case *ssa.MakeChan:
- es.Add(builtin, "makechan", cs)
- }
- return
- }
-
- var analyzeBasicBlock func(*ssa.BasicBlock) []Escapes // Recursive.
- analyzeBasicBlock = func(block *ssa.BasicBlock) (rval []Escapes) {
- for _, inst := range block.Instrs {
- if es := analyzeInstruction(inst); !es.IsEmpty() {
- rval = append(rval, es)
- }
- }
- return
- }
-
- loadFunc = func(fn *ssa.Function) Escapes {
- // Is this already available?
- name := fn.RelString(pass.Pkg)
- if es, ok := mergedEscapes[name]; ok {
- return es
- }
-
- // In the case of a true cycle, we assume that the current
- // function itself has no escapes.
- //
- // When evaluating the function again, the proper escapes will
- // be filled in here.
- allEscapes[name] = nil
- mergedEscapes[name] = Escapes{}
-
- // Perform the basic analysis.
- var es []Escapes
- if fn.Recover != nil {
- es = append(es, analyzeBasicBlock(fn.Recover)...)
- }
- for _, block := range fn.Blocks {
- es = append(es, analyzeBasicBlock(block)...)
- }
-
- // Check for a stack split.
- if call, has := hasCall(fn); has {
- var ss Escapes
- ss.Add(stackSplit, call, CallSite{
- LocalPos: fn.Pos(),
- Resolved: linePosition(fn, fn.Parent()),
- })
- es = append(es, ss)
- }
-
- // Save the result and return.
- //
- // Note that we merge the result when saving to the facts. It
- // doesn't really matter the specific escapes, as long as we
- // have recorded all the appropriate classes of escapes.
- summary := MergeAll(es)
- allEscapes[name] = es
- mergedEscapes[name] = summary
- return summary
- }
-
- // Complete all local functions.
- for _, fn := range state.SrcFuncs {
- loadFunc(fn)
- }
-
- if !localEscapes {
- // Export all findings for future packages. We only do this in
- // non-local escapes mode, and expect to run this analysis
- // after the SelectAnalysis.
- pass.ExportPackageFact(&packageEscapeFacts{
- Funcs: mergedEscapes,
- })
- }
-
- // Scan all functions for violations.
- for _, f := range pass.Files {
- // Scan all declarations.
- for _, decl := range f.Decls {
- // Function declaration?
- fdecl, ok := decl.(*ast.FuncDecl)
- if !ok {
- continue
- }
- var (
- reasons []EscapeReason
- local bool
- testReasons map[EscapeReason]bool
- )
- if localEscapes {
- // Find all hard escapes.
- reasons = hardReasons
- } else {
- // Find all declared reasons.
- reasons, local, testReasons = findReasons(pass, fdecl)
- }
-
- // Scan for matches.
- fn := pass.TypesInfo.Defs[fdecl.Name].(*types.Func)
- fv := state.Pkg.Prog.FuncValue(fn)
- if fv == nil {
- continue
- }
- name := fv.RelString(pass.Pkg)
- all, allOk := allEscapes[name]
- merged, mergedOk := mergedEscapes[name]
- if !allOk || !mergedOk {
- pass.Reportf(fdecl.Pos(), "internal error: function %s not found.", name)
- continue
- }
-
- // Filter reasons and report.
- //
- // For the findings, we use all escapes.
- for _, es := range all {
- es.Filter(reasons, local)
- es.Reportf(pass)
- }
-
- // Scan for test (required) matches.
- //
- // For tests we need only the merged escapes.
- testReasonsFound := make(map[EscapeReason]bool)
- for r := EscapeReason(0); r < reasonCount; r++ {
- if merged.CallSites[r] == nil {
- continue
- }
- // Is this local?
- wantLocal, ok := testReasons[r]
- isLocal := len(merged.CallSites[r]) == 1
- testReasonsFound[r] = isLocal
- if !ok {
- continue
- }
- if isLocal == wantLocal {
- delete(testReasons, r)
- }
- }
- for reason, local := range testReasons {
- // We didn't find the escapes we wanted.
- pass.Reportf(fdecl.Pos(), fmt.Sprintf("testescapes not found: reason=%s, local=%t", reason, local))
- }
- if len(testReasons) > 0 {
- // Report for debugging.
- merged.Reportf(pass)
- }
- }
- }
-
- return nil, nil
-}