Handle integer conversion errors (#425)

When converting a double larger or equal to math.MaxInt64 to an integer, it previously overflowed silently. It now returns an explicit error.

This fixes along the way, the math.abs(-9223372036854775808) issue.
This commit is contained in:
Jean Hadrien Chabran
2021-07-25 18:25:02 +02:00
committed by GitHub
parent daf4f79e9f
commit 01c87d1bf7
4 changed files with 54 additions and 14 deletions

View File

@@ -14,11 +14,6 @@ func CastAs(v types.Value, t types.ValueType) (types.Value, error) {
return v, nil
}
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
switch t {
case types.BoolValue:
return CastAsBool(v)
@@ -45,6 +40,11 @@ func CastAs(v types.Value, t types.ValueType) (types.Value, error) {
// it fails if the text doesn't contain a valid boolean.
// Any other type is considered an invalid cast.
func CastAsBool(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
switch v.Type() {
case types.BoolValue:
return v, nil
@@ -70,6 +70,11 @@ func CastAsBool(v types.Value) (types.Value, error) {
// It fails if the text doesn't contain a valid float value.
// Any other type is considered an invalid cast.
func CastAsInteger(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
switch v.Type() {
case types.IntegerValue:
return v, nil
@@ -79,7 +84,11 @@ func CastAsInteger(v types.Value) (types.Value, error) {
}
return types.NewIntegerValue(0), nil
case types.DoubleValue:
return types.NewIntegerValue(int64(v.V().(float64))), nil
f := v.V().(float64)
if f > 0 && int64(f) < 0 {
return nil, stringutil.Errorf("integer out of range")
}
return types.NewIntegerValue(int64(f)), nil
case types.TextValue:
i, err := strconv.ParseInt(v.V().(string), 10, 64)
if err != nil {
@@ -102,6 +111,11 @@ func CastAsInteger(v types.Value) (types.Value, error) {
// it fails if the text doesn't contain a valid float value.
// Any other type is considered an invalid cast.
func CastAsDouble(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
switch v.Type() {
case types.DoubleValue:
return v, nil
@@ -121,6 +135,11 @@ func CastAsDouble(v types.Value) (types.Value, error) {
// CastAsText returns a JSON representation of v.
// If the representation is a string, it gets unquoted.
func CastAsText(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
switch v.Type() {
case types.TextValue:
return v, nil
@@ -142,6 +161,11 @@ func CastAsText(v types.Value) (types.Value, error) {
// Text: decodes a base64 string, otherwise fails.
// Any other type is considered an invalid cast.
func CastAsBlob(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
if v.Type() == types.BlobValue {
return v, nil
}
@@ -164,6 +188,11 @@ func CastAsBlob(v types.Value) (types.Value, error) {
// Text: decodes a JSON array, otherwise fails.
// Any other type is considered an invalid cast.
func CastAsArray(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
if v.Type() == types.ArrayValue {
return v, nil
}
@@ -185,6 +214,11 @@ func CastAsArray(v types.Value) (types.Value, error) {
// Text: decodes a JSON object, otherwise fails.
// Any other type is considered an invalid cast.
func CastAsDocument(v types.Value) (types.Value, error) {
// Null values always remain null.
if v.Type() == types.NullValue {
return v, nil
}
if v.Type() == types.DocumentValue {
return v, nil
}

View File

@@ -1,6 +1,7 @@
package document
import (
"math"
"testing"
"github.com/genjidb/genji/types"
@@ -70,6 +71,7 @@ func TestCastAs(t *testing.T) {
{blobV, nil, true},
{arrayV, nil, true},
{docV, nil, true},
{types.NewDoubleValue(math.MaxInt64 + 1), nil, true},
})
})

View File

@@ -17,6 +17,8 @@ NULL
2.0
! math.abs('foo')
'cannot cast "foo" as double'
! math.abs(-9223372036854775808)
'integer out of range'
-- test: math.acos
> math.acos(NULL)

View File

@@ -152,7 +152,7 @@ func ExprRunner(t *testing.T, testfile string) {
require.NoError(t, err)
// eval it to get a proper Value
want, err := e.Eval(emptyEnv)
want, err := e.Eval(environment.New(nil))
require.NoError(t, err)
// parse the given expr
@@ -160,7 +160,7 @@ func ExprRunner(t *testing.T, testfile string) {
require.NoError(t, err)
// eval it to get a proper Value
got, err := e.Eval(emptyEnv)
got, err := e.Eval(environment.New(nil))
require.NoError(t, err)
// finally, compare those two
@@ -170,12 +170,14 @@ func ExprRunner(t *testing.T, testfile string) {
t.Run("NOK "+stmt.Expr, func(t *testing.T) {
// parse the given epxr
e, err := parser.NewParser(strings.NewReader(stmt.Expr)).ParseExpr()
require.NoError(t, err)
if err != nil {
require.Regexp(t, regexp.MustCompile(regexp.QuoteMeta(stmt.Res)), err.Error())
} else {
// eval it, it should return an error
_, err = e.Eval(emptyEnv)
_, err = e.Eval(environment.New(nil))
require.NotNilf(t, err, "expected expr `%s` to return an error, got nil", stmt.Expr)
require.Regexp(t, regexp.MustCompile(regexp.QuoteMeta(stmt.Res)), err.Error())
}
})
}
}