lang: types: Fix ValueOf() panic with nil pointer values

Some forms of reflect.Value can cause ValueOf() to panic when there is a
nil pointer somewhere within the reflect.Value, whether that be a
container type like a struct, list or map, or just a raw nil pointer.

In these cases, ValueOf() attempted to dereference the pointer without
ever checking if it was nil. mgmt lang doesn't have pointers of any
kind, so these Golang values cannot be represented in mcl types in the
current form so return a helpful error to the user.

Signed-off-by: Joe Groocock <me@frebib.net>
This commit is contained in:
Joe Groocock
2021-05-02 10:05:12 +01:00
committed by James Shubin
parent 4d187419ac
commit 29f1c6f50e
2 changed files with 40 additions and 1 deletions

View File

@@ -18,6 +18,7 @@
package types package types
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
@@ -27,6 +28,16 @@ import (
"github.com/purpleidea/mgmt/util/errwrap" "github.com/purpleidea/mgmt/util/errwrap"
) )
var (
// ErrNilValue is returned when ValueOf() attempts to represent a nil
// pointer as an mcl value. This is not supported in mcl.
ErrNilValue = errors.New("cannot represent a nil Golang value in mcl")
// ErrInvalidValue is returned when ValueOf() is called on an invalid or
// zero reflect.Value.
ErrInvalidValue = errors.New("cannot represent invalid reflect.Value")
)
// Value represents an interface to get values out of each type. It is similar // Value represents an interface to get values out of each type. It is similar
// to the reflection interfaces used in the golang standard library. // to the reflection interfaces used in the golang standard library.
type Value interface { type Value interface {
@@ -65,12 +76,22 @@ func ValueOfGolang(i interface{}) (Value, error) {
// a resource field. This is done with our "elvis" operator. When using this // a resource field. This is done with our "elvis" operator. When using this
// function, if you pass in something with a nil value, then expect a panic or // function, if you pass in something with a nil value, then expect a panic or
// an error if you're lucky. // an error if you're lucky.
// FIXME: we should make sure we error instead of ever panic-ing on a nil value
func ValueOf(v reflect.Value) (Value, error) { func ValueOf(v reflect.Value) (Value, error) {
// Gracefully handle invalid values instead of panic(). Invalid
// reflect.Value values can come from nil values, or the zero value.
if !v.IsValid() {
return nil, ErrInvalidValue
}
value := v value := v
typ := value.Type() typ := value.Type()
kind := typ.Kind() kind := typ.Kind()
for kind == reflect.Ptr { for kind == reflect.Ptr {
// Prevent panic() if value is a nil pointer and return an error.
if value.IsNil() {
return nil, ErrNilValue
}
typ = typ.Elem() // un-nest one pointer typ = typ.Elem() // un-nest one pointer
kind = typ.Kind() kind = typ.Kind()

View File

@@ -20,6 +20,7 @@
package types package types
import ( import (
"errors"
"fmt" "fmt"
"math" "math"
"reflect" "reflect"
@@ -694,6 +695,23 @@ func TestValueOf2(t *testing.T) {
} }
} }
func TestValueOf3(t *testing.T) {
st := struct {
Ptr *string `lang:"ptr"`
Ptr2 **string `lang:"ptr2"`
}{nil, nil}
// cannot represent nil pointers, expect an err
val, err := ValueOf(reflect.ValueOf(st))
if err == nil {
t.Errorf("function ValueOf(%+v) returned a Value when error was expected: %s", st, val)
return
}
if !errors.Is(err, ErrNilValue) {
t.Errorf("function ValueOf(%+v) returned err %s but types.ErrNilValue was expected", st, err)
}
}
func TestValueInto0(t *testing.T) { func TestValueInto0(t *testing.T) {
// converts a Go variable to a types.Value, or panics if any error // converts a Go variable to a types.Value, or panics if any error
mustValue := func(v interface{}) Value { mustValue := func(v interface{}) Value {