@@ -10,6 +10,7 @@ import (
1010 "go/build"
1111 "regexp"
1212 "strings"
13+ "unicode"
1314
1415 "golang.org/x/tools/go/analysis"
1516 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
@@ -24,15 +25,97 @@ var Analyzer = &analysis.Analyzer{
2425 Run : run ,
2526}
2627
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+ }
33115
34116func 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 {
36119 return nil , nil
37120 }
38121 if build .Default .GOOS != "linux" && build .Default .GOOS != "darwin" {
@@ -63,6 +146,9 @@ func run(pass *analysis.Pass) (interface{}, error) {
63146 line = line [:i ]
64147 }
65148 line = strings .TrimSpace (line )
149+ if line == "" {
150+ continue
151+ }
66152
67153 // We start checking code at a TEXT line for a frameless function.
68154 if strings .HasPrefix (line , "TEXT" ) && strings .Contains (line , "(SB)" ) && strings .Contains (line , "$0" ) {
@@ -73,16 +159,12 @@ func run(pass *analysis.Pass) (interface{}, error) {
73159 continue
74160 }
75161
76- if asmWriteBP . MatchString (line ) { // clobber of BP, function is not OK
162+ if arch . isFPWrite (line ) {
77163 pass .Reportf (analysisutil .LineStart (tf , lineno ), "frame pointer is clobbered before saving" )
78164 active = false
79165 continue
80166 }
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 ) {
86168 active = false
87169 continue
88170 }
0 commit comments