Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package framepointer defines an Analyzer that reports assembly code
     6  // that clobbers the frame pointer before saving it.
     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  // 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  	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, // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
    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  				// 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  		isUnconditionalBranch: 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 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  	// Find assembly files to work on.
    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  			// Ignore comments and commented-out code.
   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  			// We start checking code at a TEXT line for a frameless function.
   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