mitchellh/hashstructure

visit() visits unexported fields and panics

xiam opened this issue · 1 comments

xiam commented

Hello @mitchellh,

Thanks for publishing this library. I ran into this panic trace while trying it out:

panic: reflect.Value.Interface: cannot return value obtained from unexported field or method [recovered]
        panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

goroutine 25 [running]:
testing.tRunner.func1(0xc8200ac360)
        /usr/local/go/src/testing/testing.go:450 +0x171
reflect.valueInterface(0x12e2a0, 0xc82010a030, 0x79, 0xc82010c001, 0x0, 0x0)
        /usr/local/go/src/reflect/value.go:912 +0xe7
reflect.Value.Interface(0x12e2a0, 0xc82010a030, 0x79, 0x0, 0x0)
        /usr/local/go/src/reflect/value.go:901 +0x48
github.com/mitchellh/hashstructure.(*walker).visit(0xc8200c5d90, 0x12e2a0, 0xc82010a030, 0x79, 0xc8201080f0, 0xaf63bd4c8601b7a9, 0x0, 0x0)
        /home/rev/go/src/github.com/mitchellh/hashstructure/hashstructure.go:193 +0x10fe
...

On line 193 of hashstructure.go we have:

parent := v.Interface()

In this case the Interface() method will panic if v is an unexported field.

According to the hashstructure docs:

Unexported fields on structs are ignored and do not affect the hash value.

But this is not accurate, while exported fields with tag hash:"ignore" are
effectively ignored unexported fields are visit()ed anyway, and when
Interface() is called on an unexported struct field the visit function panics.

You can replicate said error with the following snippet:

type structWithoutExportedFields struct {
  v struct{}
}

Hash(structWithoutExportedFields{}, nil)

We could call CanInterface() before casting it, but we can also avoid the
whole situation by checking if the field is unexported and skipping it, just
like what you do with tag:"ignore".

Good catch. Thanks! Fixed.