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 | } |
102 |
Members