github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/internal/lint/adapter/analyzer.go (about)

     1  package adapter
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  	"strings"
     8  
     9  	"golang.org/x/tools/go/analysis"
    10  	"golang.org/x/tools/go/analysis/passes/inspect"
    11  	"golang.org/x/tools/go/ast/inspector"
    12  )
    13  
    14  const (
    15  	defaultProviderPackage = "github.com/khulnasoft-lab/defsec/pkg/providers"
    16  	defaultTypesPackage    = "github.com/khulnasoft-lab/defsec/pkg/types"
    17  )
    18  
    19  func DefaultAnalyzer() *analysis.Analyzer {
    20  	return CreateAnalyzer(defaultProviderPackage, defaultTypesPackage)
    21  }
    22  
    23  func CreateAnalyzer(providerPackage, typesPackage string) *analysis.Analyzer {
    24  	return &analysis.Analyzer{
    25  		Name:     "adapter",
    26  		Doc:      "reports provider struct initialisations with omitted 'types' fields",
    27  		Requires: []*analysis.Analyzer{inspect.Analyzer},
    28  		Run: func(pass *analysis.Pass) (interface{}, error) {
    29  
    30  			if err := detectNamedFunctionReturns(pass, providerPackage); err != nil {
    31  				return nil, err
    32  			}
    33  
    34  			if err := detectEmptyStructInit(pass, providerPackage); err != nil {
    35  				return nil, err
    36  			}
    37  
    38  			if err := detectMissingTypesFields(pass, providerPackage, typesPackage); err != nil {
    39  				return nil, err
    40  			}
    41  
    42  			if err := detectMetadataLiterals(pass, typesPackage); err != nil {
    43  				return nil, err
    44  			}
    45  
    46  			return nil, nil
    47  		},
    48  	}
    49  }
    50  
    51  func detectMetadataLiterals(pass *analysis.Pass, typesPackage string) error {
    52  	insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    53  	insp.Preorder([]ast.Node{
    54  		(*ast.CompositeLit)(nil),
    55  	}, func(n ast.Node) {
    56  
    57  		if strings.HasSuffix(pass.Fset.File(n.Pos()).Name(), "_test.go") {
    58  			return
    59  		}
    60  
    61  		lit := n.(*ast.CompositeLit)
    62  		if lit.Type == nil {
    63  			return
    64  		}
    65  
    66  		tx := pass.TypesInfo.TypeOf(lit.Type)
    67  		if tx == nil {
    68  			return
    69  		}
    70  
    71  		if !strings.HasPrefix(tx.String(), typesPackage) {
    72  			return
    73  		}
    74  
    75  		named, ok := tx.(*types.Named)
    76  		if !ok {
    77  			return
    78  		}
    79  
    80  		if named.String() == fmt.Sprintf("%s.Metadata", typesPackage) {
    81  			pass.Reportf(lit.Pos(), "Metadata instances should not be initialised using literals")
    82  		}
    83  	})
    84  	return nil
    85  
    86  }
    87  
    88  func detectNamedFunctionReturns(pass *analysis.Pass, providerPackage string) error {
    89  	for _, f := range pass.Files {
    90  		for _, d := range f.Decls {
    91  			switch fnc := d.(type) {
    92  			case *ast.FuncDecl:
    93  				if fnc.Type.Results == nil {
    94  					continue
    95  				}
    96  				for _, result := range fnc.Type.Results.List {
    97  					for _, name := range result.Names {
    98  						if field, ok := name.Obj.Decl.(*ast.Field); ok {
    99  							tx := pass.TypesInfo.TypeOf(field.Type)
   100  							if tx == nil {
   101  								continue
   102  							}
   103  
   104  							if !strings.HasPrefix(tx.String(), providerPackage) {
   105  								continue
   106  							}
   107  
   108  							if strings.HasSuffix(pass.Fset.File(field.Pos()).Name(), "_test.go") {
   109  								continue
   110  							}
   111  
   112  							pass.Reportf(field.Pos(), "Provider struct %s should not be initialised via a named function return type", tx.String())
   113  						}
   114  					}
   115  				}
   116  			}
   117  		}
   118  	}
   119  	return nil
   120  }
   121  
   122  func detectEmptyStructInit(pass *analysis.Pass, providerPackage string) error {
   123  	insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   124  	insp.Preorder([]ast.Node{
   125  		(*ast.DeclStmt)(nil),
   126  	}, func(n ast.Node) {
   127  
   128  		if strings.HasSuffix(pass.Fset.File(n.Pos()).Name(), "_test.go") {
   129  			return
   130  		}
   131  
   132  		decl := n.(*ast.DeclStmt)
   133  		gen, ok := decl.Decl.(*ast.GenDecl)
   134  		if !ok {
   135  			return
   136  		}
   137  		for _, spec := range gen.Specs {
   138  			u, ok := spec.(*ast.ValueSpec)
   139  			if !ok {
   140  				continue
   141  			}
   142  
   143  			tx := pass.TypesInfo.TypeOf(u.Type)
   144  			if tx == nil {
   145  				return
   146  			}
   147  
   148  			if !strings.HasPrefix(tx.String(), providerPackage) {
   149  				continue
   150  			}
   151  			pass.Reportf(n.Pos(), "Provider struct %s should be explicitly initialised with all fields provided", tx.String())
   152  		}
   153  	})
   154  	return nil
   155  }
   156  func detectMissingTypesFields(pass *analysis.Pass, providerPackage, typesPackage string) error {
   157  	insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   158  	insp.Preorder([]ast.Node{
   159  		(*ast.CompositeLit)(nil),
   160  	}, func(n ast.Node) {
   161  
   162  		if strings.HasSuffix(pass.Fset.File(n.Pos()).Name(), "_test.go") {
   163  			return
   164  		}
   165  
   166  		lit := n.(*ast.CompositeLit)
   167  		if lit.Type == nil {
   168  			return
   169  		}
   170  
   171  		tx := pass.TypesInfo.TypeOf(lit.Type)
   172  		if tx == nil {
   173  			return
   174  		}
   175  
   176  		if !strings.HasPrefix(tx.String(), providerPackage) {
   177  			return
   178  		}
   179  
   180  		named, ok := tx.(*types.Named)
   181  		if !ok {
   182  			return
   183  		}
   184  
   185  		switch u := named.Underlying().(type) {
   186  		case *types.Struct:
   187  			for i := 0; i < u.NumFields(); i++ {
   188  				field := u.Field(i)
   189  				if !strings.HasPrefix(field.Type().String(), typesPackage) {
   190  					continue
   191  				}
   192  				var found bool
   193  				for j, included := range lit.Elts {
   194  					switch et := included.(type) {
   195  					case *ast.KeyValueExpr:
   196  						if et.Key.(*ast.Ident).Name == field.Name() {
   197  							found = true
   198  						}
   199  					default:
   200  						found = found || i == j
   201  					}
   202  					if found {
   203  						break
   204  					}
   205  				}
   206  				if !found {
   207  					pass.Reportf(lit.Pos(), "Provider struct %s is missing an initialised value for field '%s'", named.String(), field.Name())
   208  				}
   209  			}
   210  		}
   211  	})
   212  	return nil
   213  }