diff options
Diffstat (limited to 'tools/checkescape/checkescape.go')
-rw-r--r-- | tools/checkescape/checkescape.go | 918 |
1 files changed, 0 insertions, 918 deletions
diff --git a/tools/checkescape/checkescape.go b/tools/checkescape/checkescape.go deleted file mode 100644 index 8eeabbc3d..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 which *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": {}, - "runtime.duffcopy": {}, - "runtime.racefuncenter": {}, - "runtime.gcWriteBarrier": {}, - "runtime.retpolineAX": {}, - "runtime.retpolineBP": {}, - "runtime.retpolineBX": {}, - "runtime.retpolineCX": {}, - "runtime.retpolineDI": {}, - "runtime.retpolineDX": {}, - "runtime.retpolineR10": {}, - "runtime.retpolineR11": {}, - "runtime.retpolineR12": {}, - "runtime.retpolineR13": {}, - "runtime.retpolineR14": {}, - "runtime.retpolineR15": {}, - "runtime.retpolineR8": {}, - "runtime.retpolineR9": {}, - "runtime.retpolineSI": {}, - "runtime.stackcheck": {}, - "runtime.settls": {}, - } - 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, callsErr := loadObjdump() - if callsErr != 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", callsErr) - } - 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 callsErr != nil { - // See above: we don't have access to the binary - // itself, so need to include every possible call. - return fmt.Sprintf("(possible, unable to load objdump: %v)", callsErr), 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 -} |