...

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

Documentation: golang.org/x/tools/go/analysis/passes/framepointer

     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  	Run:  run,
    24  }
    25  
    26  var (
    27  	re             = regexp.MustCompile
    28  	asmWriteBP     = re(`,\s*BP$`) // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
    29  	asmMentionBP   = re(`\bBP\b`)
    30  	asmControlFlow = re(`^(J|RET)`)
    31  )
    32  
    33  func run(pass *analysis.Pass) (interface{}, error) {
    34  	if build.Default.GOARCH != "amd64" { // TODO: arm64 also?
    35  		return nil, nil
    36  	}
    37  	if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
    38  		return nil, nil
    39  	}
    40  
    41  	// Find assembly files to work on.
    42  	var sfiles []string
    43  	for _, fname := range pass.OtherFiles {
    44  		if strings.HasSuffix(fname, ".s") && pass.Pkg.Path() != "runtime" {
    45  			sfiles = append(sfiles, fname)
    46  		}
    47  	}
    48  
    49  	for _, fname := range sfiles {
    50  		content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  
    55  		lines := strings.SplitAfter(string(content), "\n")
    56  		active := false
    57  		for lineno, line := range lines {
    58  			lineno++
    59  
    60  			// Ignore comments and commented-out code.
    61  			if i := strings.Index(line, "//"); i >= 0 {
    62  				line = line[:i]
    63  			}
    64  			line = strings.TrimSpace(line)
    65  
    66  			// We start checking code at a TEXT line for a frameless function.
    67  			if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
    68  				active = true
    69  				continue
    70  			}
    71  			if !active {
    72  				continue
    73  			}
    74  
    75  			if asmWriteBP.MatchString(line) { // clobber of BP, function is not OK
    76  				pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
    77  				active = false
    78  				continue
    79  			}
    80  			if asmMentionBP.MatchString(line) { // any other use of BP might be a read, so function is OK
    81  				active = false
    82  				continue
    83  			}
    84  			if asmControlFlow.MatchString(line) { // give up after any branch instruction
    85  				active = false
    86  				continue
    87  			}
    88  		}
    89  	}
    90  	return nil, nil
    91  }
    92  

View as plain text