Skip to content

Commit 058c483

Browse files
committed
Add check for structs without any exported fields
1 parent ec0bee2 commit 058c483

File tree

4 files changed

+79
-7
lines changed

4 files changed

+79
-7
lines changed

errchkjson.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,16 @@ func (e *errchkjson) handleJSONMarshal(pass *analysis.Pass, n ast.Node, fnName s
110110
t = t.(*types.Pointer).Elem()
111111
}
112112

113-
err := jsonSafe(t)
113+
err := jsonSafe(t, 0)
114114
if err != nil {
115115
if _, ok := err.(unsupported); ok {
116116
pass.Reportf(n.Pos(), "`%s` for %v", fnName, err)
117117
return
118118
}
119+
if _, ok := err.(noexported); ok {
120+
pass.Reportf(n.Pos(), "Error argument passed to `%s` does not contain any exported field", fnName)
121+
}
122+
// Only care about unsafe types if they are assigned to the blank identifier.
119123
if blankIdentifier {
120124
pass.Reportf(n.Pos(), "Error return value of `%s` is not checked: %v", fnName, err)
121125
}
@@ -135,7 +139,7 @@ const (
135139
unsupportedBasicTypes = types.IsComplex
136140
)
137141

138-
func jsonSafe(t types.Type) error {
142+
func jsonSafe(t types.Type, level int) error {
139143
if types.Implements(t, textMarshalerInterface()) {
140144
return fmt.Errorf("unsafe type `%s` found", t.String())
141145
}
@@ -162,20 +166,21 @@ func jsonSafe(t types.Type) error {
162166
}
163167

164168
case *types.Array:
165-
err := jsonSafe(ut.Elem())
169+
err := jsonSafe(ut.Elem(), level+1)
166170
if err != nil {
167171
return err
168172
}
169173
return nil
170174

171175
case *types.Slice:
172-
err := jsonSafe(ut.Elem())
176+
err := jsonSafe(ut.Elem(), level+1)
173177
if err != nil {
174178
return err
175179
}
176180
return nil
177181

178182
case *types.Struct:
183+
exported := 0
179184
for i := 0; i < ut.NumFields(); i++ {
180185
if !ut.Field(i).Exported() {
181186
// Unexported fields can be ignored
@@ -187,15 +192,19 @@ func jsonSafe(t types.Type) error {
187192
continue
188193
}
189194
}
190-
err := jsonSafe(ut.Field(i).Type())
195+
err := jsonSafe(ut.Field(i).Type(), level+1)
191196
if err != nil {
192197
return err
193198
}
199+
exported++
200+
}
201+
if level == 0 && exported == 0 {
202+
return newNoexportedError(fmt.Errorf("struct does not export any field"))
194203
}
195204
return nil
196205

197206
case *types.Pointer:
198-
err := jsonSafe(ut.Elem())
207+
err := jsonSafe(ut.Elem(), level+1)
199208
if err != nil {
200209
return err
201210
}
@@ -206,7 +215,7 @@ func jsonSafe(t types.Type) error {
206215
if err != nil {
207216
return err
208217
}
209-
err = jsonSafe(ut.Elem())
218+
err = jsonSafe(ut.Elem(), level+1)
210219
if err != nil {
211220
return err
212221
}

errchkjson_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,13 @@ func TestOmitSafeFlag(t *testing.T) {
2525
testdata := analysistest.TestData()
2626
analysistest.Run(t, testdata, errchkjson, "nosafe")
2727
}
28+
29+
func TestNoExportedField(t *testing.T) {
30+
errchkjson := errchkjson.NewAnalyzer()
31+
err := errchkjson.Flags.Set("omit-safe", "true")
32+
if err != nil {
33+
t.Fatalf("error setting 'omit-safe' command line flag: %v", err)
34+
}
35+
testdata := analysistest.TestData()
36+
analysistest.Run(t, testdata, errchkjson, "noexport")
37+
}

noexported_error.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package errchkjson
2+
3+
type noexported interface {
4+
noexported()
5+
}
6+
7+
var _ noexported = noexportedError{}
8+
9+
type noexportedError struct {
10+
err error
11+
}
12+
13+
func newNoexportedError(err error) error {
14+
return noexportedError{
15+
err: err,
16+
}
17+
}
18+
19+
func (u noexportedError) noexported() {}
20+
21+
func (u noexportedError) Error() string {
22+
return u.err.Error()
23+
}

testdata/src/noexport/a.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package example
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
// JSONMarshalStructWithoutExportedFields contains a struct without exported fields.
8+
func JSONMarshalStructWithoutExportedFields() {
9+
var err error
10+
11+
var withoutExportedFields struct {
12+
privateField bool
13+
ExportedButOmittedField bool `json:"-"`
14+
}
15+
_, err = json.Marshal(withoutExportedFields) // want "Error argument passed to `encoding/json.Marshal` does not contain any exported field"
16+
_ = err
17+
}
18+
19+
// JSONMarshalStructWithoutExportedFields contains a struct without exported fields.
20+
func JSONMarshalStructWithNestedStructWithoutExportedFields() {
21+
var err error
22+
23+
var withNestedStructWithoutExportedFields struct {
24+
ExportedStruct struct {
25+
privatField bool
26+
}
27+
}
28+
_, err = json.Marshal(withNestedStructWithoutExportedFields)
29+
_ = err
30+
}

0 commit comments

Comments
 (0)