onsi / gomega

Ginkgo's Preferred Matcher Library

Home Page:http://onsi.github.io/gomega/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Boxed errors that are nil can trigger a panic in format.Object

matthchr opened this issue · comments

Repro code here:


// CustomError exists just to showcase the behavior. Ignore that it's a pointless error type.
type CustomError struct {
	Details string
}

func (c *CustomError) Error() string {
	return c.Details
}

var _ error = &CustomError{}

func Function1(fails bool) error {
	if fails {
		return &CustomError{
			Details: "an error",
		}
	}

	return nil
}

func Function2(fails bool) *CustomError {
	if fails {
		return &CustomError{
			Details: "an error",
		}
	}

	return nil
}

func Test_Repro1(t *testing.T) {
	g := NewGomegaWithT(t)

	g.Expect(Function1(true)).ToNot(HaveOccurred())
}

func Test_Repro2(t *testing.T) {
	g := NewGomegaWithT(t)

	g.Expect(Function1(false)).To(HaveOccurred())
}

func Test_Repro3(t *testing.T) {
	g := NewGomegaWithT(t)

	g.Expect(Function2(true)).ToNot(HaveOccurred())
}

func Test_Repro4(t *testing.T) {
	g := NewGomegaWithT(t)

	g.Expect(Function2(false)).To(HaveOccurred())
}

Test_Repro4 panics with the following traceback:

=== RUN   Test_Repro4
--- FAIL: Test_Repro4 (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x5871a0]

goroutine 19 [running]:
testing.tRunner.func1.2({0x5a9dc0, 0x76ef00})
	/usr/local/go1.20/src/testing/testing.go:1526 +0x24e
testing.tRunner.func1()
	/usr/local/go1.20/src/testing/testing.go:1529 +0x39f
panic({0x5a9dc0, 0x76ef00})
	/usr/local/go1.20/src/runtime/panic.go:884 +0x213
gomegarepro.(*CustomError).Error(0x5dc627?)
	/home/matthchr/work/temp/gomegarepro/lib_test.go:15
github.com/onsi/gomega/format.Object({0x5a5b60, 0x0}, 0x4d4ce5?)
	/home/matthchr/go/pkg/mod/github.com/onsi/gomega@v1.27.8/format/format.go:263 +0x159
github.com/onsi/gomega/matchers.(*HaveOccurredMatcher).FailureMessage(0xc00003e648?, {0x5a5b60?, 0x0?})
	/home/matthchr/go/pkg/mod/github.com/onsi/gomega@v1.27.8/matchers/have_occurred_matcher.go:30 +0x2e
github.com/onsi/gomega/internal.(*Assertion).match(0xc0000f4c00, {0x63d640, 0x7c0148}, 0x1, {0x0, 0x0, 0x0})
	/home/matthchr/go/pkg/mod/github.com/onsi/gomega@v1.27.8/internal/assertion.go:101 +0x128
github.com/onsi/gomega/internal.(*Assertion).To(0xc0000f4c00, {0x63d640, 0x7c0148}, {0x0, 0x0, 0x0})
	/home/matthchr/go/pkg/mod/github.com/onsi/gomega@v1.27.8/internal/assertion.go:62 +0xb5
gomegarepro.Test_Repro4(0x0?)
	/home/matthchr/work/temp/gomegarepro/lib_test.go:61 +0x65
testing.tRunner(0xc000083380, 0x60e458)
	/usr/local/go1.20/src/testing/testing.go:1576 +0x10b
created by testing.(*T).Run
	/usr/local/go1.20/src/testing/testing.go:1629 +0x3ea


Process finished with the exit code 1

What actually happens for Test_Repro4 is that for this code, actual is an interface{} | *CustomError (a boxed nil).

When this is passed through to func Object(object interface{}, indentation uint) string, it passes the object.(error) cast and so err.Error() is called, which panics.

This doesn't happen for Test_Repro2 (which calls an identical function to Test_Repro4 except the return type is error rather than a concrete type that implements error) because error is already an interface and so is not boxed.

A possible solution for this problem would be to ensure that the error check guards against the possibility that the error is nil, like so:

	if err, ok := object.(error); ok && !isNil(err) {
		commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
	}

If there's agreement on the suggested solution above I'm happy to send a PR.

hey @matthchr - thanks for the detailed work here. your solution sounds good to me, yes please do send in a PR!

PR is here: #681