@@ -10,6 +10,7 @@ import (
10
10
"go/build"
11
11
"regexp"
12
12
"strings"
13
+ "unicode"
13
14
14
15
"golang.org/x/tools/go/analysis"
15
16
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
@@ -24,15 +25,97 @@ var Analyzer = &analysis.Analyzer{
24
25
Run : run ,
25
26
}
26
27
27
- var (
28
- re = regexp .MustCompile
29
- asmWriteBP = re (`,\s*BP$` ) // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
30
- asmMentionBP = re (`\bBP\b` )
31
- asmControlFlow = re (`^(J|RET)` )
32
- )
28
+ // Per-architecture checks for instructions.
29
+ // Assume comments, leading and trailing spaces are removed.
30
+ type arch struct {
31
+ isFPWrite func (string ) bool
32
+ isFPRead func (string ) bool
33
+ isBranch 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 , // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
50
+ isFPRead : re (`\bBP\b` ).MatchString ,
51
+ isBranch : func (s string ) bool {
52
+ return hasAnyPrefix (s , "J" , "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
+ // Instructions which write to a pair of registers, e.g.
62
+ // LDP 8(R0), (R26, R29)
63
+ // CASPD (R2, R3), (R2), (R26, R29)
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
+ isBranch : func (s string ) bool {
74
+ // Get just the instruction
75
+ if i := strings .IndexFunc (s , unicode .IsSpace ); i > 0 {
76
+ s = s [:i ]
77
+ }
78
+ return arm64Branch [s ]
79
+ },
80
+ },
81
+ }
82
+
83
+ // arm64 has many control flow instructions.
84
+ // ^(B|RET) isn't sufficient or correct (e.g. BIC, BFI aren't control flow.)
85
+ // It's easier to explicitly enumerate them in a map than to write a regex.
86
+ // Borrowed from Go tree, cmd/asm/internal/arch/arm64.go
87
+ var arm64Branch = map [string ]bool {
88
+ "B" : true ,
89
+ "BL" : true ,
90
+ "BEQ" : true ,
91
+ "BNE" : true ,
92
+ "BCS" : true ,
93
+ "BHS" : true ,
94
+ "BCC" : true ,
95
+ "BLO" : true ,
96
+ "BMI" : true ,
97
+ "BPL" : true ,
98
+ "BVS" : true ,
99
+ "BVC" : true ,
100
+ "BHI" : true ,
101
+ "BLS" : true ,
102
+ "BGE" : true ,
103
+ "BLT" : true ,
104
+ "BGT" : true ,
105
+ "BLE" : true ,
106
+ "CBZ" : true ,
107
+ "CBZW" : true ,
108
+ "CBNZ" : true ,
109
+ "CBNZW" : true ,
110
+ "JMP" : true ,
111
+ "TBNZ" : true ,
112
+ "TBZ" : true ,
113
+ "RET" : true ,
114
+ }
33
115
34
116
func run (pass * analysis.Pass ) (interface {}, error ) {
35
- if build .Default .GOARCH != "amd64" { // TODO: arm64 also?
117
+ arch , ok := arches [build .Default .GOARCH ]
118
+ if ! ok {
36
119
return nil , nil
37
120
}
38
121
if build .Default .GOOS != "linux" && build .Default .GOOS != "darwin" {
@@ -63,6 +146,9 @@ func run(pass *analysis.Pass) (interface{}, error) {
63
146
line = line [:i ]
64
147
}
65
148
line = strings .TrimSpace (line )
149
+ if line == "" {
150
+ continue
151
+ }
66
152
67
153
// We start checking code at a TEXT line for a frameless function.
68
154
if strings .HasPrefix (line , "TEXT" ) && strings .Contains (line , "(SB)" ) && strings .Contains (line , "$0" ) {
@@ -73,16 +159,12 @@ func run(pass *analysis.Pass) (interface{}, error) {
73
159
continue
74
160
}
75
161
76
- if asmWriteBP . MatchString (line ) { // clobber of BP, function is not OK
162
+ if arch . isFPWrite (line ) {
77
163
pass .Reportf (analysisutil .LineStart (tf , lineno ), "frame pointer is clobbered before saving" )
78
164
active = false
79
165
continue
80
166
}
81
- if asmMentionBP .MatchString (line ) { // any other use of BP might be a read, so function is OK
82
- active = false
83
- continue
84
- }
85
- if asmControlFlow .MatchString (line ) { // give up after any branch instruction
167
+ if arch .isFPRead (line ) || arch .isBranch (line ) {
86
168
active = false
87
169
continue
88
170
}
0 commit comments