Skip to content

Commit

Permalink
Merge pull request #11631 from vegaprotocol/hotfix-cumulated-err-recu…
Browse files Browse the repository at this point in the history
…rsion

fix: check for circular references
  • Loading branch information
EVODelavega authored Aug 31, 2024
2 parents f1e6e81 + 017abc1 commit 6431330
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
42 changes: 41 additions & 1 deletion libs/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@

package errors

import "strings"
import (
"fmt"
"strings"
)

var (
errSelfReference = fmt.Errorf("<self reference>")
errParentReference = fmt.Errorf("<parent reference>")
)

type CumulatedErrors struct {
Errors []error
Expand All @@ -26,9 +34,41 @@ func NewCumulatedErrors() *CumulatedErrors {
}

func (e *CumulatedErrors) Add(err error) {
// prevent adding this instance of cumulatedErrors to itself.
if err == e {
err = errSelfReference
} else if cerr, ok := err.(*CumulatedErrors); ok {
// nothing to add.
if !cerr.HasAny() {
return
}
// create a copy of the error we're adding
cpy := &CumulatedErrors{
Errors: append([]error{}, cerr.Errors...),
}
// remove any references to the parent from the error we're adding.
err = cpy.checkRef(e, errParentReference)
}
e.Errors = append(e.Errors, err)
}

// check recursively if a cumulated errors object contains a certain reference, and if so, replace with a placehold, simple error.
// returns either itself (with the replaced references), or a replacement error.
func (e *CumulatedErrors) checkRef(ref *CumulatedErrors, repl error) error {
if e == ref {
return repl
}
// recursively remove a given reference.
for i, subE := range e.Errors {
if subE == ref {
e.Errors[i] = repl
} else if cErr, ok := subE.(*CumulatedErrors); ok {
e.Errors[i] = cErr.checkRef(ref, repl)
}
}
return e
}

func (e *CumulatedErrors) HasAny() bool {
return len(e.Errors) > 0
}
Expand Down
69 changes: 69 additions & 0 deletions libs/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (C) 2023 Gobalsky Labs Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package errors_test

import (
"fmt"
"testing"

"code.vegaprotocol.io/vega/libs/errors"

"github.com/stretchr/testify/require"
)

func TestCircularreferences(t *testing.T) {
parent := errors.NewCumulatedErrors()
child := errors.NewCumulatedErrors()
nested := errors.NewCumulatedErrors()
errs := []error{
fmt.Errorf("simple error 1"),
fmt.Errorf("simple error 2"),
fmt.Errorf("simple error 3"),
}
t.Run("try to add parent to itself", func(t *testing.T) {
parent.Add(parent)
expect := "<self reference>"
require.True(t, parent.HasAny())
require.Equal(t, expect, parent.Error())
})
t.Run("try nesting without circular references", func(t *testing.T) {
child.Add(errs[0])
parent.Add(child)
expect := fmt.Sprintf("<self reference>, also %s", errs[0].Error())
require.Equal(t, expect, parent.Error())
})
t.Run("try adding empty cumulated error", func(t *testing.T) {
parent.Add(nested)
// still the same expected value
expect := fmt.Sprintf("<self reference>, also %s", errs[0].Error())
require.False(t, nested.HasAny())
require.Equal(t, expect, parent.Error())
// adding to the nested error should not affect the parent.
nested.Add(errs[1])
require.True(t, nested.HasAny())
require.Equal(t, expect, parent.Error())
})
t.Run("try nesting both parent and child, and adding to both", func(t *testing.T) {
nested.Add(errs[2])
nested.Add(child)
nested.Add(parent)
child.Add(nested)
parent.Add(nested)
// self reference>, also simple error 1, also simple error 2, also simple error 3, also simple error 1, also <self reference>, also simple error 1
expect := fmt.Sprintf("<self reference>, also %s, also %s, also %s, also %s, also <self reference>, also %s", errs[0].Error(), errs[1].Error(), errs[2].Error(), errs[0].Error(), errs[0].Error())
require.Equal(t, expect, parent.Error())
})
}

0 comments on commit 6431330

Please sign in to comment.