github.com/prysmaticlabs/prysm@v1.4.4/tools/analyzers/interfacechecker/analyzer.go (about)

     1  // Package interfacechecker implements a static analyzer to prevent incorrect conditional checks on select interfaces.
     2  package interfacechecker
     3  
     4  import (
     5  	"errors"
     6  	"go/ast"
     7  	"go/token"
     8  	"go/types"
     9  	"strings"
    10  
    11  	"golang.org/x/tools/go/analysis"
    12  	"golang.org/x/tools/go/analysis/passes/inspect"
    13  	"golang.org/x/tools/go/ast/inspector"
    14  )
    15  
    16  // Doc explaining the tool.
    17  const Doc = "Enforce usage of proper conditional check for interfaces"
    18  
    19  // Analyzer runs static analysis.
    20  var Analyzer = &analysis.Analyzer{
    21  	Name:     "interfacechecker",
    22  	Doc:      Doc,
    23  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    24  	Run:      run,
    25  }
    26  
    27  // These are the selected interfaces that we want to parse through and check nilness for.
    28  var selectedInterfaces = []string{
    29  	"interfaces.SignedBeaconBlock",
    30  	"interfaces.MetadataV0",
    31  	"interface.BeaconState",
    32  	"interface.ReadOnlyBeaconState",
    33  	"interface.WriteOnlyBeaconState",
    34  }
    35  
    36  func run(pass *analysis.Pass) (interface{}, error) {
    37  	inspection, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    38  	if !ok {
    39  		return nil, errors.New("analyzer is not type *inspector.Inspector")
    40  	}
    41  
    42  	nodeFilter := []ast.Node{
    43  		(*ast.IfStmt)(nil),
    44  	}
    45  
    46  	inspection.Preorder(nodeFilter, func(node ast.Node) {
    47  		stmt, ok := node.(*ast.IfStmt)
    48  		if !ok {
    49  			return
    50  		}
    51  		exp, ok := stmt.Cond.(*ast.BinaryExpr)
    52  		if !ok {
    53  			return
    54  		}
    55  		handleConditionalExpression(exp, pass)
    56  	})
    57  
    58  	return nil, nil
    59  }
    60  
    61  func handleConditionalExpression(exp *ast.BinaryExpr, pass *analysis.Pass) {
    62  	identX, ok := exp.X.(*ast.Ident)
    63  	if !ok {
    64  		return
    65  	}
    66  	identY, ok := exp.Y.(*ast.Ident)
    67  	if !ok {
    68  		return
    69  	}
    70  	typeMap := pass.TypesInfo.Types
    71  	if _, ok := typeMap[identX].Type.(*types.Slice); ok {
    72  		return
    73  	}
    74  	if _, ok := typeMap[identY].Type.(*types.Slice); ok {
    75  		return
    76  	}
    77  	for _, iface := range selectedInterfaces {
    78  		xIsIface := strings.Contains(typeMap[identX].Type.String(), iface)
    79  		xIsNil := typeMap[identX].IsNil()
    80  		yisIface := strings.Contains(typeMap[identY].Type.String(), iface)
    81  		yIsNil := typeMap[identY].IsNil()
    82  		// Exit early if neither are of the desired interface
    83  		if !xIsIface && !yisIface {
    84  			continue
    85  		}
    86  		if xIsIface && yIsNil {
    87  			reportFailure(identX.Pos(), pass)
    88  		}
    89  		if yisIface && xIsNil {
    90  			reportFailure(identY.Pos(), pass)
    91  		}
    92  	}
    93  }
    94  
    95  func reportFailure(pos token.Pos, pass *analysis.Pass) {
    96  	pass.Reportf(pos, "A single nilness check is being performed on an interface"+
    97  		", this check needs another accompanying nilness check on the underlying object for the interface.")
    98  }