Skip to content

Commit 4787e32

Browse files
authored
Merge pull request #3 from breml/add-no-exported-field-check
Add check for structs without any exported fields
2 parents cc97728 + 058c483 commit 4787e32

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
@@ -112,12 +112,16 @@ func (e *errchkjson) handleJSONMarshal(pass *analysis.Pass, ce *ast.CallExpr, fn
112112
t = t.(*types.Pointer).Elem()
113113
}
114114

115-
err := jsonSafe(t)
115+
err := jsonSafe(t, 0)
116116
if err != nil {
117117
if _, ok := err.(unsupported); ok {
118118
pass.Reportf(ce.Pos(), "`%s` for %v", fnName, err)
119119
return
120120
}
121+
if _, ok := err.(noexported); ok {
122+
pass.Reportf(n.Pos(), "Error argument passed to `%s` does not contain any exported field", fnName)
123+
}
124+
// Only care about unsafe types if they are assigned to the blank identifier.
121125
if blankIdentifier {
122126
pass.Reportf(ce.Pos(), "Error return value of `%s` is not checked: %v", fnName, err)
123127
}
@@ -137,7 +141,7 @@ const (
137141
unsupportedBasicTypes = types.IsComplex
138142
)
139143

140-
func jsonSafe(t types.Type) error {
144+
func jsonSafe(t types.Type, level int) error {
141145
if types.Implements(t, textMarshalerInterface()) {
142146
return fmt.Errorf("unsafe type `%s` found", t.String())
143147
}
@@ -164,20 +168,21 @@ func jsonSafe(t types.Type) error {
164168
}
165169

166170
case *types.Array:
167-
err := jsonSafe(ut.Elem())
171+
err := jsonSafe(ut.Elem(), level+1)
168172
if err != nil {
169173
return err
170174
}
171175
return nil
172176

173177
case *types.Slice:
174-
err := jsonSafe(ut.Elem())
178+
err := jsonSafe(ut.Elem(), level+1)
175179
if err != nil {
176180
return err
177181
}
178182
return nil
179183

180184
case *types.Struct:
185+
exported := 0
181186
for i := 0; i < ut.NumFields(); i++ {
182187
if !ut.Field(i).Exported() {
183188
// Unexported fields can be ignored
@@ -189,15 +194,19 @@ func jsonSafe(t types.Type) error {
189194
continue
190195
}
191196
}
192-
err := jsonSafe(ut.Field(i).Type())
197+
err := jsonSafe(ut.Field(i).Type(), level+1)
193198
if err != nil {
194199
return err
195200
}
201+
exported++
202+
}
203+
if level == 0 && exported == 0 {
204+
return newNoexportedError(fmt.Errorf("struct does not export any field"))
196205
}
197206
return nil
198207

199208
case *types.Pointer:
200-
err := jsonSafe(ut.Elem())
209+
err := jsonSafe(ut.Elem(), level+1)
201210
if err != nil {
202211
return err
203212
}
@@ -208,7 +217,7 @@ func jsonSafe(t types.Type) error {
208217
if err != nil {
209218
return err
210219
}
211-
err = jsonSafe(ut.Elem())
220+
err = jsonSafe(ut.Elem(), level+1)
212221
if err != nil {
213222
return err
214223
}

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)