gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/securego/gosec/rules/errors.go (about)

     1  // (c) Copyright 2016 Hewlett Packard Enterprise Development LP
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rules
    16  
    17  import (
    18  	"go/ast"
    19  	"go/types"
    20  
    21  	"github.com/securego/gosec"
    22  )
    23  
    24  type noErrorCheck struct {
    25  	gosec.MetaData
    26  	whitelist gosec.CallList
    27  }
    28  
    29  func (r *noErrorCheck) ID() string {
    30  	return r.MetaData.ID
    31  }
    32  
    33  func returnsError(callExpr *ast.CallExpr, ctx *gosec.Context) int {
    34  	if tv := ctx.Info.TypeOf(callExpr); tv != nil {
    35  		switch t := tv.(type) {
    36  		case *types.Tuple:
    37  			for pos := 0; pos < t.Len(); pos++ {
    38  				variable := t.At(pos)
    39  				if variable != nil && variable.Type().String() == "error" {
    40  					return pos
    41  				}
    42  			}
    43  		case *types.Named:
    44  			if t.String() == "error" {
    45  				return 0
    46  			}
    47  		}
    48  	}
    49  	return -1
    50  }
    51  
    52  func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, error) {
    53  	switch stmt := n.(type) {
    54  	case *ast.AssignStmt:
    55  		for _, expr := range stmt.Rhs {
    56  			if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx) == nil {
    57  				pos := returnsError(callExpr, ctx)
    58  				if pos < 0 || pos >= len(stmt.Lhs) {
    59  					return nil, nil
    60  				}
    61  				if id, ok := stmt.Lhs[pos].(*ast.Ident); ok && id.Name == "_" {
    62  					return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil
    63  				}
    64  			}
    65  		}
    66  	case *ast.ExprStmt:
    67  		if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil {
    68  			pos := returnsError(callExpr, ctx)
    69  			if pos >= 0 {
    70  				return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil
    71  			}
    72  		}
    73  	}
    74  	return nil, nil
    75  }
    76  
    77  // NewNoErrorCheck detects if the returned error is unchecked
    78  func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
    79  	// TODO(gm) Come up with sensible defaults here. Or flip it to use a
    80  	// black list instead.
    81  	whitelist := gosec.NewCallList()
    82  	whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString")
    83  	whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln")
    84  	whitelist.Add("io.PipeWriter", "CloseWithError")
    85  
    86  	if configured, ok := conf["G104"]; ok {
    87  		if whitelisted, ok := configured.(map[string][]string); ok {
    88  			for key, val := range whitelisted {
    89  				whitelist.AddAll(key, val...)
    90  			}
    91  		}
    92  	}
    93  	return &noErrorCheck{
    94  		MetaData: gosec.MetaData{
    95  			ID:         id,
    96  			Severity:   gosec.Low,
    97  			Confidence: gosec.High,
    98  			What:       "Errors unhandled.",
    99  		},
   100  		whitelist: whitelist,
   101  	}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ExprStmt)(nil)}
   102  }