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  
    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$`) // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
    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" { // TODO: arm64 also?
    36  		return nil, nil
    37  	}
    38  	if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
    39  		return nil, nil
    40  	}
    41  
    42  	// Find assembly files to work on.
    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  			// Ignore comments and commented-out code.
    62  			if i := strings.Index(line, "//"); i >= 0 {
    63  				line = line[:i]
    64  			}
    65  			line = strings.TrimSpace(line)
    66  
    67  			// We start checking code at a TEXT line for a frameless function.
    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) { // clobber of BP, function is not OK
    77  				pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
    78  				active = false
    79  				continue
    80  			}
    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
    86  				active = false
    87  				continue
    88  			}
    89  		}
    90  	}
    91  	return nil, nil
    92  }
    93  

View as plain text