1
2
3
4
5
6
7 package framepointer
8
9 import (
10 "go/build"
11 "regexp"
12 "strings"
13 "unicode"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
17 )
18
19 const Doc = "report assembly that clobbers the frame pointer before saving it"
20
21 var Analyzer = &analysis.Analyzer{
22 Name: "framepointer",
23 Doc: Doc,
24 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer",
25 Run: run,
26 }
27
28
29
30 type arch struct {
31 isFPWrite func(string) bool
32 isFPRead func(string) bool
33 isUnconditionalBranch func(string) bool
34 }
35
36 var re = regexp.MustCompile
37
38 func hasAnyPrefix(s string, prefixes ...string) bool {
39 for _, p := range prefixes {
40 if strings.HasPrefix(s, p) {
41 return true
42 }
43 }
44 return false
45 }
46
47 var arches = map[string]arch{
48 "amd64": {
49 isFPWrite: re(`,\s*BP$`).MatchString,
50 isFPRead: re(`\bBP\b`).MatchString,
51 isUnconditionalBranch: func(s string) bool {
52 return hasAnyPrefix(s, "JMP", "RET")
53 },
54 },
55 "arm64": {
56 isFPWrite: func(s string) bool {
57 if i := strings.LastIndex(s, ","); i > 0 && strings.HasSuffix(s[i:], "R29") {
58 return true
59 }
60 if hasAnyPrefix(s, "LDP", "LDAXP", "LDXP", "CASP") {
61
62
63
64 lp := strings.LastIndex(s, "(")
65 rp := strings.LastIndex(s, ")")
66 if lp > -1 && lp < rp {
67 return strings.Contains(s[lp:rp], ",") && strings.Contains(s[lp:rp], "R29")
68 }
69 }
70 return false
71 },
72 isFPRead: re(`\bR29\b`).MatchString,
73 isUnconditionalBranch: func(s string) bool {
74
75 if i := strings.IndexFunc(s, unicode.IsSpace); i > 0 {
76 s = s[:i]
77 }
78 return s == "B" || s == "JMP" || s == "RET"
79 },
80 },
81 }
82
83 func run(pass *analysis.Pass) (any, error) {
84 arch, ok := arches[build.Default.GOARCH]
85 if !ok {
86 return nil, nil
87 }
88 if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
89 return nil, nil
90 }
91
92
93 var sfiles []string
94 for _, fname := range pass.OtherFiles {
95 if strings.HasSuffix(fname, ".s") && pass.Pkg.Path() != "runtime" {
96 sfiles = append(sfiles, fname)
97 }
98 }
99
100 for _, fname := range sfiles {
101 content, tf, err := analysisutil.ReadFile(pass, fname)
102 if err != nil {
103 return nil, err
104 }
105
106 lines := strings.SplitAfter(string(content), "\n")
107 active := false
108 for lineno, line := range lines {
109 lineno++
110
111
112 if i := strings.Index(line, "//"); i >= 0 {
113 line = line[:i]
114 }
115 line = strings.TrimSpace(line)
116 if line == "" {
117 continue
118 }
119
120
121 if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
122 active = true
123 continue
124 }
125 if !active {
126 continue
127 }
128
129 if arch.isFPWrite(line) {
130 pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
131 active = false
132 continue
133 }
134 if arch.isFPRead(line) || arch.isUnconditionalBranch(line) {
135 active = false
136 continue
137 }
138 }
139 }
140 return nil, nil
141 }
142
View as plain text