github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/unmarshal/unmarshal.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The unmarshal package defines an Analyzer that checks for passing
     6  // non-pointer or non-interface types to unmarshal and decode functions.
     7  package unmarshal
     8  
     9  import (
    10  	"go/ast"
    11  	"go/types"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  	"golang.org/x/tools/go/ast/inspector"
    16  	"golang.org/x/tools/go/types/typeutil"
    17  	"golang.org/x/tools/internal/typeparams"
    18  )
    19  
    20  const Doc = `report passing non-pointer or non-interface values to unmarshal
    21  
    22  The unmarshal analysis reports calls to functions such as json.Unmarshal
    23  in which the argument type is not a pointer or an interface.`
    24  
    25  var Analyzer = &analysis.Analyzer{
    26  	Name:     "unmarshal",
    27  	Doc:      Doc,
    28  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    29  	Run:      run,
    30  }
    31  
    32  func run(pass *analysis.Pass) (interface{}, error) {
    33  	switch pass.Pkg.Path() {
    34  	case "encoding/gob", "encoding/json", "encoding/xml", "encoding/asn1":
    35  		// These packages know how to use their own APIs.
    36  		// Sometimes they are testing what happens to incorrect programs.
    37  		return nil, nil
    38  	}
    39  
    40  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  
    42  	nodeFilter := []ast.Node{
    43  		(*ast.CallExpr)(nil),
    44  	}
    45  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    46  		call := n.(*ast.CallExpr)
    47  		fn := typeutil.StaticCallee(pass.TypesInfo, call)
    48  		if fn == nil {
    49  			return // not a static call
    50  		}
    51  
    52  		// Classify the callee (without allocating memory).
    53  		argidx := -1
    54  		recv := fn.Type().(*types.Signature).Recv()
    55  		if fn.Name() == "Unmarshal" && recv == nil {
    56  			// "encoding/json".Unmarshal
    57  			// "encoding/xml".Unmarshal
    58  			// "encoding/asn1".Unmarshal
    59  			switch fn.Pkg().Path() {
    60  			case "encoding/json", "encoding/xml", "encoding/asn1":
    61  				argidx = 1 // func([]byte, interface{})
    62  			}
    63  		} else if fn.Name() == "Decode" && recv != nil {
    64  			// (*"encoding/json".Decoder).Decode
    65  			// (* "encoding/gob".Decoder).Decode
    66  			// (* "encoding/xml".Decoder).Decode
    67  			t := recv.Type()
    68  			if ptr, ok := t.(*types.Pointer); ok {
    69  				t = ptr.Elem()
    70  			}
    71  			tname := t.(*types.Named).Obj()
    72  			if tname.Name() == "Decoder" {
    73  				switch tname.Pkg().Path() {
    74  				case "encoding/json", "encoding/xml", "encoding/gob":
    75  					argidx = 0 // func(interface{})
    76  				}
    77  			}
    78  		}
    79  		if argidx < 0 {
    80  			return // not a function we are interested in
    81  		}
    82  
    83  		if len(call.Args) < argidx+1 {
    84  			return // not enough arguments, e.g. called with return values of another function
    85  		}
    86  
    87  		t := pass.TypesInfo.Types[call.Args[argidx]].Type
    88  		switch t.Underlying().(type) {
    89  		case *types.Pointer, *types.Interface, *typeparams.TypeParam:
    90  			return
    91  		}
    92  
    93  		switch argidx {
    94  		case 0:
    95  			pass.Reportf(call.Lparen, "call of %s passes non-pointer", fn.Name())
    96  		case 1:
    97  			pass.Reportf(call.Lparen, "call of %s passes non-pointer as second argument", fn.Name())
    98  		}
    99  	})
   100  	return nil, nil
   101  }