From a1a85f0aba38c8dd8f0df7839bd67d3ffe29fac2 Mon Sep 17 00:00:00 2001 From: Markus Date: Thu, 3 Feb 2022 08:05:52 +0000 Subject: [PATCH] Fix handling of looping structs * tests: add case for looping structure (stack overflow) * Add tracking for already seen structs Fixes #8 Co-authored-by: Lucas Bremgartner --- errchkjson.go | 19 ++++++++++++------- errchkjson_test.go | 7 +++++++ testdata/src/loop/a.go | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 testdata/src/loop/a.go diff --git a/errchkjson.go b/errchkjson.go index 4d6d45e..d3c0b71 100644 --- a/errchkjson.go +++ b/errchkjson.go @@ -120,7 +120,7 @@ func (e *errchkjson) handleJSONMarshal(pass *analysis.Pass, ce *ast.CallExpr, fn t = t.(*types.Pointer).Elem() } - err := e.jsonSafe(t, 0) + err := e.jsonSafe(t, 0, map[types.Type]struct{}{}) if err != nil { if _, ok := err.(unsupported); ok { pass.Reportf(ce.Pos(), "`%s` for %v", fnName, err) @@ -149,7 +149,11 @@ const ( unsupportedBasicTypes = types.IsComplex ) -func (e *errchkjson) jsonSafe(t types.Type, level int) error { +func (e *errchkjson) jsonSafe(t types.Type, level int, seenTypes map[types.Type]struct{}) error { + if _, ok := seenTypes[t]; ok { + return nil + } + if types.Implements(t, textMarshalerInterface()) { return fmt.Errorf("unsafe type `%s` found", t.String()) } @@ -176,20 +180,21 @@ func (e *errchkjson) jsonSafe(t types.Type, level int) error { } case *types.Array: - err := e.jsonSafe(ut.Elem(), level+1) + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) if err != nil { return err } return nil case *types.Slice: - err := e.jsonSafe(ut.Elem(), level+1) + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) if err != nil { return err } return nil case *types.Struct: + seenTypes[t] = struct{}{} exported := 0 for i := 0; i < ut.NumFields(); i++ { if !ut.Field(i).Exported() { @@ -202,7 +207,7 @@ func (e *errchkjson) jsonSafe(t types.Type, level int) error { continue } } - err := e.jsonSafe(ut.Field(i).Type(), level+1) + err := e.jsonSafe(ut.Field(i).Type(), level+1, seenTypes) if err != nil { return err } @@ -214,7 +219,7 @@ func (e *errchkjson) jsonSafe(t types.Type, level int) error { return nil case *types.Pointer: - err := e.jsonSafe(ut.Elem(), level+1) + err := e.jsonSafe(ut.Elem(), level+1, seenTypes) if err != nil { return err } @@ -225,7 +230,7 @@ func (e *errchkjson) jsonSafe(t types.Type, level int) error { if err != nil { return err } - err = e.jsonSafe(ut.Elem(), level+1) + err = e.jsonSafe(ut.Elem(), level+1, seenTypes) if err != nil { return err } diff --git a/errchkjson_test.go b/errchkjson_test.go index a1a0f31..1b15fe9 100644 --- a/errchkjson_test.go +++ b/errchkjson_test.go @@ -39,3 +39,10 @@ func TestNoExportedField(t *testing.T) { testdata := analysistest.TestData() analysistest.Run(t, testdata, errchkjson, "noexport") } + +func TestLoop(t *testing.T) { + errchkjson := errchkjson.NewAnalyzer() + + testdata := analysistest.TestData() + analysistest.Run(t, testdata, errchkjson, "loop") +} diff --git a/testdata/src/loop/a.go b/testdata/src/loop/a.go new file mode 100644 index 0000000..4accc72 --- /dev/null +++ b/testdata/src/loop/a.go @@ -0,0 +1,18 @@ +package example + +import ( + "encoding/json" +) + +type entry struct { + Name string `json:"name"` + Fields schema `json:"fields"` +} + +type schema []entry + +// JSONMarshalStructWithLoop contains a struct with a loop. +func JSONMarshalStructWithLoop() { + var structWithLoop schema + _, _ = json.Marshal(structWithLoop) +}