aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/ghostiam
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/ghostiam')
-rw-r--r--vendor/github.com/ghostiam/protogetter/.goreleaser.yaml24
-rw-r--r--vendor/github.com/ghostiam/protogetter/LICENSE21
-rw-r--r--vendor/github.com/ghostiam/protogetter/Makefile9
-rw-r--r--vendor/github.com/ghostiam/protogetter/README.md73
-rw-r--r--vendor/github.com/ghostiam/protogetter/posfilter.go65
-rw-r--r--vendor/github.com/ghostiam/protogetter/processor.go234
-rw-r--r--vendor/github.com/ghostiam/protogetter/protogetter.go183
7 files changed, 609 insertions, 0 deletions
diff --git a/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml b/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml
new file mode 100644
index 000000000..a70d0fb00
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/.goreleaser.yaml
@@ -0,0 +1,24 @@
+before:
+ hooks:
+ - go mod tidy
+builds:
+ - id: protogetter
+ main: ./cmd/protogetter
+ binary: protogetter
+ env:
+ - CGO_ENABLED=0
+ goos:
+ - linux
+ - windows
+ - darwin
+checksum:
+ name_template: 'checksums.txt'
+snapshot:
+ name_template: "{{ incpatch .Version }}-next"
+changelog:
+ sort: asc
+ filters:
+ exclude:
+ - '^docs:'
+ - '^test:'
+ - '^ci:' \ No newline at end of file
diff --git a/vendor/github.com/ghostiam/protogetter/LICENSE b/vendor/github.com/ghostiam/protogetter/LICENSE
new file mode 100644
index 000000000..b4449661b
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Vladislav Fursov (GhostIAm)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/vendor/github.com/ghostiam/protogetter/Makefile b/vendor/github.com/ghostiam/protogetter/Makefile
new file mode 100644
index 000000000..af4b62bdf
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/Makefile
@@ -0,0 +1,9 @@
+.PHONY: test
+test:
+ cd testdata && make vendor
+ go test -v ./...
+
+.PHONY: install
+install:
+ go install ./cmd/protogetter
+ @echo "Installed in $(shell which protogetter)"
diff --git a/vendor/github.com/ghostiam/protogetter/README.md b/vendor/github.com/ghostiam/protogetter/README.md
new file mode 100644
index 000000000..c033e9597
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/README.md
@@ -0,0 +1,73 @@
+# Protogetter
+Welcome to the Protogetter project!
+
+## Overview
+Protogetter is a linter developed specifically for Go programmers working with nested `protobuf` types.\
+It's designed to aid developers in preventing `invalid memory address or nil pointer dereference` errors arising from direct access of nested `protobuf` fields.
+
+When working with `protobuf`, it's quite common to have complex structures where a message field is contained within another message, which itself can be part of another message, and so on.
+If these fields are accessed directly and some field in the call chain will not be initialized, it can result in application panic.
+
+Protogetter addresses this issue by suggesting use of getter methods for field access.
+
+## How does it work?
+Protogetter analyzes your Go code and helps detect direct `protobuf` field accesses that could give rise to panic.\
+The linter suggests using getters:
+```go
+m.GetFoo().GetBar().GetBaz()
+```
+instead of direct field access:
+```go
+m.Foo.Bar.Baz
+```
+
+And you will then only need to perform a nil check after the final call:
+```go
+if m.GetFoo().GetBar().GetBaz() != nil {
+ // Do something with m.GetFoo().GetBar().GetBaz()
+}
+```
+instead of:
+```go
+if m.Foo != nil {
+ if m.Foo.Bar != nil {
+ if m.Foo.Bar.Baz != nil {
+ // Do something with m.Foo.Bar.Baz
+ }
+ }
+}
+```
+
+or use zero values:
+
+```go
+// If one of the methods returns `nil` we will receive 0 instead of panic.
+v := m.GetFoo().GetBar().GetBaz().GetInt()
+```
+
+instead of panic:
+
+```go
+// If at least one structure in the chains is not initialized, we will get a panic.
+v := m.Foo.Bar.Baz.Int
+```
+
+which simplifies the code and makes it more reliable.
+
+## Installation
+
+```bash
+go install github.com/ghostiam/protogetter/cmd/protogetter@latest
+```
+
+## Usage
+
+To run the linter:
+```bash
+protogetter ./...
+```
+
+Or to apply suggested fixes directly:
+```bash
+protogetter --fix ./...
+```
diff --git a/vendor/github.com/ghostiam/protogetter/posfilter.go b/vendor/github.com/ghostiam/protogetter/posfilter.go
new file mode 100644
index 000000000..82075ccb1
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/posfilter.go
@@ -0,0 +1,65 @@
+package protogetter
+
+import (
+ "go/token"
+)
+
+type PosFilter struct {
+ positions map[token.Pos]struct{}
+ alreadyReplaced map[string]map[int][2]int // map[filename][line][start, end]
+}
+
+func NewPosFilter() *PosFilter {
+ return &PosFilter{
+ positions: make(map[token.Pos]struct{}),
+ alreadyReplaced: make(map[string]map[int][2]int),
+ }
+}
+
+func (f *PosFilter) IsFiltered(pos token.Pos) bool {
+ _, ok := f.positions[pos]
+ return ok
+}
+
+func (f *PosFilter) AddPos(pos token.Pos) {
+ f.positions[pos] = struct{}{}
+}
+
+func (f *PosFilter) IsAlreadyReplaced(fset *token.FileSet, pos, end token.Pos) bool {
+ filePos := fset.Position(pos)
+ fileEnd := fset.Position(end)
+
+ lines, ok := f.alreadyReplaced[filePos.Filename]
+ if !ok {
+ return false
+ }
+
+ lineRange, ok := lines[filePos.Line]
+ if !ok {
+ return false
+ }
+
+ if lineRange[0] <= filePos.Offset && fileEnd.Offset <= lineRange[1] {
+ return true
+ }
+
+ return false
+}
+
+func (f *PosFilter) AddAlreadyReplaced(fset *token.FileSet, pos, end token.Pos) {
+ filePos := fset.Position(pos)
+ fileEnd := fset.Position(end)
+
+ lines, ok := f.alreadyReplaced[filePos.Filename]
+ if !ok {
+ lines = make(map[int][2]int)
+ f.alreadyReplaced[filePos.Filename] = lines
+ }
+
+ lineRange, ok := lines[filePos.Line]
+ if ok && lineRange[0] <= filePos.Offset && fileEnd.Offset <= lineRange[1] {
+ return
+ }
+
+ lines[filePos.Line] = [2]int{filePos.Offset, fileEnd.Offset}
+}
diff --git a/vendor/github.com/ghostiam/protogetter/processor.go b/vendor/github.com/ghostiam/protogetter/processor.go
new file mode 100644
index 000000000..445f136b8
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/processor.go
@@ -0,0 +1,234 @@
+package protogetter
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "reflect"
+ "strings"
+)
+
+type processor struct {
+ info *types.Info
+ filter *PosFilter
+
+ to strings.Builder
+ from strings.Builder
+ err error
+}
+
+func Process(info *types.Info, filter *PosFilter, n ast.Node) (*Result, error) {
+ p := &processor{
+ info: info,
+ filter: filter,
+ }
+
+ return p.process(n)
+}
+
+func (c *processor) process(n ast.Node) (*Result, error) {
+ switch x := n.(type) {
+ case *ast.AssignStmt:
+ // Skip any assignment to the field.
+ for _, lhs := range x.Lhs {
+ c.filter.AddPos(lhs.Pos())
+ }
+
+ case *ast.IncDecStmt:
+ // Skip any increment/decrement to the field.
+ c.filter.AddPos(x.X.Pos())
+
+ case *ast.UnaryExpr:
+ if x.Op == token.AND {
+ // Skip all expressions when the field is used as a pointer.
+ // Because this is not direct reading, but most likely writing by pointer (for example like sql.Scan).
+ c.filter.AddPos(x.X.Pos())
+ }
+
+ case *ast.CallExpr:
+ f, ok := x.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return &Result{}, nil
+ }
+
+ if !isProtoMessage(c.info, f.X) {
+ return &Result{}, nil
+ }
+
+ c.processInner(x)
+
+ case *ast.SelectorExpr:
+ if !isProtoMessage(c.info, x.X) {
+ // If the selector is not on a proto message, skip it.
+ return &Result{}, nil
+ }
+
+ c.processInner(x)
+
+ default:
+ return nil, fmt.Errorf("not implemented for type: %s (%s)", reflect.TypeOf(x), formatNode(n))
+ }
+
+ if c.err != nil {
+ return nil, c.err
+ }
+
+ return &Result{
+ From: c.from.String(),
+ To: c.to.String(),
+ }, nil
+}
+
+func (c *processor) processInner(expr ast.Expr) {
+ switch x := expr.(type) {
+ case *ast.Ident:
+ c.write(x.Name)
+
+ case *ast.BasicLit:
+ c.write(x.Value)
+
+ case *ast.UnaryExpr:
+ if x.Op == token.AND {
+ c.write(formatNode(x))
+ return
+ }
+
+ c.write(x.Op.String())
+ c.processInner(x.X)
+
+ case *ast.SelectorExpr:
+ c.processInner(x.X)
+ c.write(".")
+
+ // If getter exists, use it.
+ if methodIsExists(c.info, x.X, "Get"+x.Sel.Name) {
+ c.writeFrom(x.Sel.Name)
+ c.writeTo("Get" + x.Sel.Name + "()")
+ return
+ }
+
+ // If the selector is not a proto-message or the method has already been called, we leave it unchanged.
+ // This approach is significantly more efficient than verifying the presence of methods in all cases.
+ c.write(x.Sel.Name)
+
+ case *ast.CallExpr:
+ c.processInner(x.Fun)
+ c.write("(")
+ for i, arg := range x.Args {
+ if i > 0 {
+ c.write(",")
+ }
+ c.processInner(arg)
+ }
+ c.write(")")
+
+ case *ast.IndexExpr:
+ c.processInner(x.X)
+ c.write("[")
+ c.processInner(x.Index)
+ c.write("]")
+
+ case *ast.BinaryExpr:
+ c.processInner(x.X)
+ c.write(x.Op.String())
+ c.processInner(x.Y)
+
+ case *ast.ParenExpr:
+ c.write("(")
+ c.processInner(x.X)
+ c.write(")")
+
+ case *ast.StarExpr:
+ c.write("*")
+ c.processInner(x.X)
+
+ case *ast.CompositeLit:
+ c.write(formatNode(x))
+
+ case *ast.TypeAssertExpr:
+ c.write(formatNode(x))
+
+ default:
+ c.err = fmt.Errorf("processInner: not implemented for type: %s", reflect.TypeOf(x))
+ }
+}
+
+func (c *processor) write(s string) {
+ c.writeTo(s)
+ c.writeFrom(s)
+}
+
+func (c *processor) writeTo(s string) {
+ c.to.WriteString(s)
+}
+
+func (c *processor) writeFrom(s string) {
+ c.from.WriteString(s)
+}
+
+// Result contains source code (from) and suggested change (to)
+type Result struct {
+ From string
+ To string
+}
+
+func (r *Result) Skipped() bool {
+ // If from and to are the same, skip it.
+ return r.From == r.To
+}
+
+func isProtoMessage(info *types.Info, expr ast.Expr) bool {
+ // First, we are checking for the presence of the ProtoReflect method which is currently being generated
+ // and corresponds to v2 version.
+ // https://pkg.go.dev/google.golang.org/protobuf@v1.31.0/proto#Message
+ const protoV2Method = "ProtoReflect"
+ ok := methodIsExists(info, expr, protoV2Method)
+ if ok {
+ return true
+ }
+
+ // Afterwards, we are checking the ProtoMessage method. All the structures that implement the proto.Message interface
+ // have a ProtoMessage method and are proto-structures. This interface has been generated since version 1.0.0 and
+ // continues to exist for compatibility.
+ // https://pkg.go.dev/github.com/golang/protobuf/proto?utm_source=godoc#Message
+ const protoV1Method = "ProtoMessage"
+ ok = methodIsExists(info, expr, protoV1Method)
+ if ok {
+ // Since there is a protoc-gen-gogo generator that implements the proto.Message interface, but may not generate
+ // getters or generate from without checking for nil, so even if getters exist, we skip them.
+ const protocGenGoGoMethod = "MarshalToSizedBuffer"
+ return !methodIsExists(info, expr, protocGenGoGoMethod)
+ }
+
+ return false
+}
+
+func methodIsExists(info *types.Info, x ast.Expr, name string) bool {
+ if info == nil {
+ return false
+ }
+
+ t := info.TypeOf(x)
+ if t == nil {
+ return false
+ }
+
+ ptr, ok := t.Underlying().(*types.Pointer)
+ if ok {
+ t = ptr.Elem()
+ }
+
+ named, ok := t.(*types.Named)
+ if !ok {
+ return false
+ }
+
+ for i := 0; i < named.NumMethods(); i++ {
+ if named.Method(i).Name() == name {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/ghostiam/protogetter/protogetter.go b/vendor/github.com/ghostiam/protogetter/protogetter.go
new file mode 100644
index 000000000..80a829672
--- /dev/null
+++ b/vendor/github.com/ghostiam/protogetter/protogetter.go
@@ -0,0 +1,183 @@
+package protogetter
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/format"
+ "go/token"
+ "log"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+type Mode int
+
+const (
+ StandaloneMode Mode = iota
+ GolangciLintMode
+)
+
+const msgFormat = "avoid direct access to proto field %s, use %s instead"
+
+func NewAnalyzer() *analysis.Analyzer {
+ return &analysis.Analyzer{
+ Name: "protogetter",
+ Doc: "Reports direct reads from proto message fields when getters should be used",
+ Run: func(pass *analysis.Pass) (any, error) {
+ Run(pass, StandaloneMode)
+ return nil, nil
+ },
+ }
+}
+
+func Run(pass *analysis.Pass, mode Mode) []Issue {
+ nodeTypes := []ast.Node{
+ (*ast.AssignStmt)(nil),
+ (*ast.CallExpr)(nil),
+ (*ast.SelectorExpr)(nil),
+ (*ast.IncDecStmt)(nil),
+ (*ast.UnaryExpr)(nil),
+ }
+
+ // Skip protoc-generated files.
+ var files []*ast.File
+ for _, f := range pass.Files {
+ if !isProtocGeneratedFile(f) {
+ files = append(files, f)
+
+ // ast.Print(pass.Fset, f)
+ }
+ }
+
+ ins := inspector.New(files)
+
+ var issues []Issue
+
+ filter := NewPosFilter()
+ ins.Preorder(nodeTypes, func(node ast.Node) {
+ report := analyse(pass, filter, node)
+ if report == nil {
+ return
+ }
+
+ switch mode {
+ case StandaloneMode:
+ pass.Report(report.ToDiagReport())
+ case GolangciLintMode:
+ issues = append(issues, report.ToIssue(pass.Fset))
+ }
+ })
+
+ return issues
+}
+
+func analyse(pass *analysis.Pass, filter *PosFilter, n ast.Node) *Report {
+ // fmt.Printf("\n>>> check: %s\n", formatNode(n))
+ // ast.Print(pass.Fset, n)
+ if filter.IsFiltered(n.Pos()) {
+ // fmt.Printf(">>> filtered\n")
+ return nil
+ }
+
+ result, err := Process(pass.TypesInfo, filter, n)
+ if err != nil {
+ pass.Report(analysis.Diagnostic{
+ Pos: n.Pos(),
+ End: n.End(),
+ Message: fmt.Sprintf("error: %v", err),
+ })
+
+ return nil
+ }
+
+ // If existing in filter, skip it.
+ if filter.IsFiltered(n.Pos()) {
+ return nil
+ }
+
+ if result.Skipped() {
+ return nil
+ }
+
+ // If the expression has already been replaced, skip it.
+ if filter.IsAlreadyReplaced(pass.Fset, n.Pos(), n.End()) {
+ return nil
+ }
+ // Add the expression to the filter.
+ filter.AddAlreadyReplaced(pass.Fset, n.Pos(), n.End())
+
+ return &Report{
+ node: n,
+ result: result,
+ }
+}
+
+// Issue is used to integrate with golangci-lint's inline auto fix.
+type Issue struct {
+ Pos token.Position
+ Message string
+ InlineFix InlineFix
+}
+
+type InlineFix struct {
+ StartCol int // zero-based
+ Length int
+ NewString string
+}
+
+type Report struct {
+ node ast.Node
+ result *Result
+}
+
+func (r *Report) ToDiagReport() analysis.Diagnostic {
+ msg := fmt.Sprintf(msgFormat, r.result.From, r.result.To)
+
+ return analysis.Diagnostic{
+ Pos: r.node.Pos(),
+ End: r.node.End(),
+ Message: msg,
+ SuggestedFixes: []analysis.SuggestedFix{
+ {
+ Message: msg,
+ TextEdits: []analysis.TextEdit{
+ {
+ Pos: r.node.Pos(),
+ End: r.node.End(),
+ NewText: []byte(r.result.To),
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *Report) ToIssue(fset *token.FileSet) Issue {
+ msg := fmt.Sprintf(msgFormat, r.result.From, r.result.To)
+ return Issue{
+ Pos: fset.Position(r.node.Pos()),
+ Message: msg,
+ InlineFix: InlineFix{
+ StartCol: fset.Position(r.node.Pos()).Column - 1,
+ Length: len(r.result.From),
+ NewString: r.result.To,
+ },
+ }
+}
+
+func isProtocGeneratedFile(f *ast.File) bool {
+ return len(f.Comments) > 0 && strings.HasPrefix(f.Comments[0].Text(), "Code generated by protoc-gen-go")
+}
+
+func formatNode(node ast.Node) string {
+ buf := new(bytes.Buffer)
+ if err := format.Node(buf, token.NewFileSet(), node); err != nil {
+ log.Printf("Error formatting expression: %v", err)
+ return ""
+ }
+
+ return buf.String()
+}