1
2
3
4
5
6
7 package framepointer
8
9 import (
10 "go/build"
11 "regexp"
12 "strings"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
16 )
17
18 const Doc = "report assembly that clobbers the frame pointer before saving it"
19
20 var Analyzer = &analysis.Analyzer{
21 Name: "framepointer",
22 Doc: Doc,
23 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer",
24 Run: run,
25 }
26
27 var (
28 re = regexp.MustCompile
29 asmWriteBP = re(`,\s*BP$`)
30 asmMentionBP = re(`\bBP\b`)
31 asmControlFlow = re(`^(J|RET)`)
32 )
33
34 func run(pass *analysis.Pass) (interface{}, error) {
35 if build.Default.GOARCH != "amd64" {
36 return nil, nil
37 }
38 if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
39 return nil, nil
40 }
41
42
43 var sfiles []string
44 for _, fname := range pass.OtherFiles {
45 if strings.HasSuffix(fname, ".s") && pass.Pkg.Path() != "runtime" {
46 sfiles = append(sfiles, fname)
47 }
48 }
49
50 for _, fname := range sfiles {
51 content, tf, err := analysisutil.ReadFile(pass, fname)
52 if err != nil {
53 return nil, err
54 }
55
56 lines := strings.SplitAfter(string(content), "\n")
57 active := false
58 for lineno, line := range lines {
59 lineno++
60
61
62 if i := strings.Index(line, "//"); i >= 0 {
63 line = line[:i]
64 }
65 line = strings.TrimSpace(line)
66
67
68 if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
69 active = true
70 continue
71 }
72 if !active {
73 continue
74 }
75
76 if asmWriteBP.MatchString(line) {
77 pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
78 active = false
79 continue
80 }
81 if asmMentionBP.MatchString(line) {
82 active = false
83 continue
84 }
85 if asmControlFlow.MatchString(line) {
86 active = false
87 continue
88 }
89 }
90 }
91 return nil, nil
92 }
93
View as plain text