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

View File

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

View File

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

View File

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