From a30a6a7b53831c79eb0b55887d2c76bb6edd846e Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Tue, 17 Feb 2015 15:26:10 +0000 Subject: [PATCH] Improve method call parameter processing Add support for the final parameter to a variadic function to be a slice. Add support for conversion of slice parameters. Expand numeric conversion support to include all int and uint types as souce types. This allows the result of bitwise calculations (int32) to be passed in as parameters. --- reflect_test.go | 31 ++++++- runtime.go | 224 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 200 insertions(+), 55 deletions(-) diff --git a/reflect_test.go b/reflect_test.go index 3c5daa5..6abaacb 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -116,6 +116,16 @@ func (abs _abcStruct) Func2IntVariadic(s string, a ...int) string { return fmt.Sprintf("%v:%v", s, t) } +func (abs _abcStruct) Func2IntArrayVariadic(s string, a ...[]int) string { + t := 0 + for _, i := range a { + for _, j := range i { + t += j + } + } + return fmt.Sprintf("%v:%v", s, t) +} + type _mnoStruct struct { Ghi string } @@ -234,6 +244,10 @@ func Test_reflectStruct(t *testing.T) { abc.Func1Int(1); `, 2) + test(` + abc.Func1Int(0x01 & 0x01); + `, 2) + test(`raise: abc.Func1Int(1.1); `, "reflect: Call using float64 as type int") @@ -294,6 +308,22 @@ func Test_reflectStruct(t *testing.T) { test(` abc.Func2IntVariadic("test", 1, 2); `, "test:3") + + test(` + abc.Func2IntVariadic("test", [1, 2]); + `, "test:3") + + test(` + abc.Func2IntArrayVariadic("test", [1, 2]); + `, "test:3") + + test(` + abc.Func2IntArrayVariadic("test", [1, 2], [3, 4]); + `, "test:10") + + test(` + abc.Func2IntArrayVariadic("test", [[1, 2], [3, 4]]); + `, "test:10") } }) } @@ -478,7 +508,6 @@ func Test_reflectArray(t *testing.T) { is(abc[len(abc)-1], false) // ... } - // []int32 { abc := make([]int32, 4) diff --git a/runtime.go b/runtime.go index be994bd..78e3892 100644 --- a/runtime.go +++ b/runtime.go @@ -2,6 +2,8 @@ package otto import ( "errors" + "fmt" + "math" "reflect" "sync" @@ -191,65 +193,146 @@ func (self *_runtime) safeToValue(value interface{}) (Value, error) { return result, err } -// safeNumericConvert converts numeric parameter val from js to the type that the function fn expects if its safe to do so. -// This allows literals (int64) and the general js numeric form (float64) to be passed as parameters to go functions easily. -func safeNumericConvert(fn reflect.Type, i int, val interface{}) reflect.Value { - switch val.(type) { - default: - // Not a supported conversion - return reflect.ValueOf(val) - case float64, int64: - // What type is the func expecting? - var ptype reflect.Type - switch { - case fn.IsVariadic() && fn.NumIn() <= i+1: - // This argument is variadic so use the variadics element type. - ptype = fn.In(fn.NumIn() - 1).Elem() - case fn.NumIn() > i: - ptype = fn.In(i) - } +// convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics. +// This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily. +func convertNumeric(val reflect.Value, t reflect.Type) reflect.Value { + if val.Kind() == t.Kind() { + return val + } - if f64, ok := val.(float64); ok { - switch ptype.Kind() { - case reflect.Float64: - return reflect.ValueOf(val) - case reflect.Float32: - if reflect.Zero(ptype).OverflowFloat(f64) { - // Not safe to convert - return reflect.ValueOf(val) - } + if val.Kind() == reflect.Interface { + val = reflect.ValueOf(val.Interface()) + } - return reflect.ValueOf(val).Convert(ptype) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - i64 := int64(f64) - if float64(i64) != f64 { - // Not safe to convert - return reflect.ValueOf(val) - } - - // The float represents an integer - val = i64 - default: - // Not a supported conversion - return reflect.ValueOf(val) + switch val.Kind() { + case reflect.Float32, reflect.Float64: + f64 := val.Float() + switch t.Kind() { + case reflect.Float64: + return reflect.ValueOf(f64) + case reflect.Float32: + if reflect.Zero(t).OverflowFloat(f64) { + panic("converting float64 to float32 would overflow") } - } - i64 := val.(int64) - switch ptype.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: - if !reflect.Zero(ptype).OverflowInt(i64) { - return reflect.ValueOf(val).Convert(ptype) - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if i64 > 0 && !reflect.Zero(ptype).OverflowUint(uint64(i64)) { - return reflect.ValueOf(val).Convert(ptype) + return val.Convert(t) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + i64 := int64(f64) + if float64(i64) != f64 { + panic(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t)) } + + // The float represents an integer + val = reflect.ValueOf(i64) + default: + panic(fmt.Sprintf("cannot convert %v to %v", val.Type(), t)) } } - // Not a supported conversion - return reflect.ValueOf(val) + switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i64 := val.Int() + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if reflect.Zero(t).OverflowInt(i64) { + panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)) + } + return val.Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if i64 < 0 { + panic(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t)) + } + if reflect.Zero(t).OverflowUint(uint64(i64)) { + panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)) + } + return val.Convert(t) + } + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + u64 := val.Uint() + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) { + panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)) + } + return val.Convert(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if reflect.Zero(t).OverflowUint(u64) { + panic(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)) + } + return val.Convert(t) + } + } + + panic(fmt.Sprintf("unsupported type %v for numeric conversion", val.Type())) +} + +// callParamConvert converts request val to type t if possible. +// If the conversion fails due to overflow or type miss-match then it panics. +// If no conversion is known then the original value is returned. +func callParamConvert(val reflect.Value, t reflect.Type) reflect.Value { + if val.Kind() == reflect.Interface { + val = reflect.ValueOf(val.Interface()) + } + + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + if val.Kind() == t.Kind() { + // Types already match + return val + } + return convertNumeric(val, t) + case reflect.Slice: + if val.Kind() != reflect.Slice { + // Conversion from none slice type to slice not possible + panic(fmt.Sprintf("cannot use %v as type %v", val, t)) + } + default: + // No supported conversion + return val + } + + elemType := t.Elem() + switch elemType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Slice: + // Attempt to convert to slice of the type t + s := reflect.MakeSlice(reflect.SliceOf(elemType), val.Len(), val.Len()) + for i := 0; i < val.Len(); i++ { + s.Index(i).Set(callParamConvert(val.Index(i), elemType)) + } + + return s + } + + // Not a slice type we can convert + return val +} + +// callSliceRequired returns true if CallSlice is required instead of Call. +func callSliceRequired(param reflect.Type, val reflect.Value) bool { + vt := val.Type() + for param.Kind() == reflect.Slice { + if val.Kind() == reflect.Interface { + val = reflect.ValueOf(val.Interface()) + vt = val.Type() + } + + if vt.Kind() != reflect.Slice { + return false + } + + vt = vt.Elem() + if val.Kind() != reflect.Invalid { + if val.Len() > 0 { + val = val.Index(0) + } else { + val = reflect.Value{} + } + } + param = param.Elem() + } + + return true } func (self *_runtime) toValue(value interface{}) Value { @@ -278,13 +361,46 @@ func (self *_runtime) toValue(value interface{}) Value { case reflect.Func: // TODO Maybe cache this? return toValue_object(self.newNativeFunction("", func(call FunctionCall) Value { - in := make([]reflect.Value, len(call.ArgumentList)) + argsCount := len(call.ArgumentList) + in := make([]reflect.Value, argsCount) t := value.Type() + callSlice := false + paramsCount := t.NumIn() + lastParam := paramsCount - 1 + lastArg := argsCount - 1 + isVariadic := t.IsVariadic() for i, value := range call.ArgumentList { - in[i] = safeNumericConvert(t, i, value.export()) + var paramType reflect.Type + if isVariadic && i == lastArg && argsCount == paramsCount { + // Variadic functions last parameter and parameter numbers match incoming args + paramType = t.In(lastArg) + val := reflect.ValueOf(value.export()) + callSlice = callSliceRequired(paramType, val) + if callSlice { + in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType) + continue + } + } + + if i >= lastParam { + if isVariadic { + paramType = t.In(lastParam).Elem() + } else { + paramType = t.In(lastParam) + } + } else { + paramType = t.In(i) + } + in[i] = callParamConvert(reflect.ValueOf(value.export()), paramType) + } + + var out []reflect.Value + if callSlice { + out = value.CallSlice(in) + } else { + out = value.Call(in) } - out := value.Call(in) l := len(out) switch l { case 0: