1package rules
2
3import (
4	"fmt"
5	"go/ast"
6	"strings"
7
8	"github.com/securego/gosec/v2"
9)
10
11type deferType struct {
12	typ     string
13	methods []string
14}
15
16type badDefer struct {
17	gosec.MetaData
18	types []deferType
19}
20
21func (r *badDefer) ID() string {
22	return r.MetaData.ID
23}
24
25func normalize(typ string) string {
26	return strings.TrimPrefix(typ, "*")
27}
28
29func contains(methods []string, method string) bool {
30	for _, m := range methods {
31		if m == method {
32			return true
33		}
34	}
35	return false
36}
37
38func (r *badDefer) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
39	if deferStmt, ok := n.(*ast.DeferStmt); ok {
40		for _, deferTyp := range r.types {
41			if typ, method, err := gosec.GetCallInfo(deferStmt.Call, c); err == nil {
42				if normalize(typ) == deferTyp.typ && contains(deferTyp.methods, method) {
43					return gosec.NewIssue(c, n, r.ID(), fmt.Sprintf(r.What, method, typ), r.Severity, r.Confidence), nil
44				}
45			}
46		}
47
48	}
49
50	return nil, nil
51}
52
53// NewDeferredClosing detects unsafe defer of error returning methods
54func NewDeferredClosing(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
55	return &badDefer{
56		types: []deferType{
57			{
58				typ:     "os.File",
59				methods: []string{"Close"},
60			},
61		},
62		MetaData: gosec.MetaData{
63			ID:         id,
64			Severity:   gosec.Medium,
65			Confidence: gosec.High,
66			What:       "Deferring unsafe method %q on type %q",
67		},
68	}, []ast.Node{(*ast.DeferStmt)(nil)}
69}
70