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 }