diff --git a/bytecode/errors.go b/bytecode/errors.go index bca07254..31ec5b03 100644 --- a/bytecode/errors.go +++ b/bytecode/errors.go @@ -1,6 +1,7 @@ package bytecode import ( + "github.com/tucats/ego/data" "github.com/tucats/ego/errors" ) @@ -42,3 +43,25 @@ func (c *Context) error(err error, context ...interface{}) *errors.Error { return r } + +// Implement the Signal bytecode, which generates an arbitrary error return, +// using the instruction opcode. +func signalByteCode(c *Context, i interface{}) error { + if i == nil { + if v, err := c.Pop(); err != nil { + return err + } else { + i = v + } + } + + if e, ok := i.(*errors.Error); ok { + return c.error(e) + } + + if e, ok := i.(error); ok { + return c.error(errors.New(e)) + } + + return c.error(errors.Message(data.String(i))) +} diff --git a/bytecode/opcodes.go b/bytecode/opcodes.go index d5e8236b..632cf353 100644 --- a/bytecode/opcodes.go +++ b/bytecode/opcodes.go @@ -116,6 +116,7 @@ const ( Say Serialize SetThis + Signal StackCheck StaticTyping Store @@ -135,6 +136,7 @@ const ( Template Timer TryPop + TryFlush TypeOf UnWrap Wait @@ -248,6 +250,7 @@ var opcodeNames = map[Opcode]string{ Say: "Say", Serialize: "Serialize", SetThis: "SetThis", + Signal: "Signal", StackCheck: "StackCheck", StaticTyping: "StaticTyping", Stop: "Stop", @@ -269,6 +272,7 @@ var opcodeNames = map[Opcode]string{ Timer: "Timer", Try: "Try", TryPop: "TryPop", + TryFlush: "TryFlush", TypeOf: "TypeOf", UnWrap: "UnWrap", Wait: "Wait", @@ -373,6 +377,7 @@ func initializeDispatch() { dispatchTable[Say] = sayByteCode dispatchTable[Serialize] = serializeByteCode dispatchTable[SetThis] = setThisByteCode + dispatchTable[Signal] = signalByteCode dispatchTable[StackCheck] = stackCheckByteCode dispatchTable[StaticTyping] = staticTypingByteCode dispatchTable[Stop] = stopByteCode @@ -393,6 +398,7 @@ func initializeDispatch() { dispatchTable[Template] = templateByteCode dispatchTable[Timer] = timerByteCode dispatchTable[Try] = tryByteCode + dispatchTable[TryFlush] = tryFlushByteCode dispatchTable[TryPop] = tryPopByteCode dispatchTable[TypeOf] = typeOfByteCode dispatchTable[UnWrap] = unwrapByteCode diff --git a/bytecode/try.go b/bytecode/try.go index ff1fe146..825076be 100644 --- a/bytecode/try.go +++ b/bytecode/try.go @@ -102,3 +102,11 @@ func tryPopByteCode(c *Context, i interface{}) error { return nil } + +// Flush the try stack, so any subsequent errors will be signaled through instead +// of caught. +func tryFlushByteCode(c *Context, i interface{}) error { + c.tryStack = make([]tryInfo, 0) + + return nil +} diff --git a/compiler/testing.go b/compiler/testing.go index db2d8497..3087857a 100644 --- a/compiler/testing.go +++ b/compiler/testing.go @@ -25,7 +25,7 @@ func initTestType() { // Define the type receiver functions t.DefineFunction("assert", nil, TestAssert) - t.DefineFunction("fail", nil, TestFail) + t.DefineFunction("Fail", nil, TestFail) t.DefineFunction("Nil", nil, TestNil) t.DefineFunction("NotNil", nil, TestNotNil) t.DefineFunction("True", nil, TestTrue) @@ -292,8 +292,7 @@ func (c *Compiler) Assert() error { func (c *Compiler) Fail() error { _ = c.modeCheck("test") - next := c.t.Peek(1) - if next != tokenizer.DirectiveToken && next != tokenizer.SemicolonToken && next != tokenizer.EndOfTokens { + if !c.t.EndofStatement() { if err := c.emitExpression(); err != nil { return err } @@ -301,7 +300,8 @@ func (c *Compiler) Fail() error { c.b.Emit(bytecode.Push, "@fail error signal") } - c.b.Emit(bytecode.Panic, true) + c.b.Emit(bytecode.TryFlush) + c.b.Emit(bytecode.Signal, nil) return nil } @@ -338,7 +338,7 @@ func (c *Compiler) TestPass() error { return nil } -// Fail implements the @fail directive. +// Fail implements the @file directive. func (c *Compiler) File() error { fileName := c.t.Next().Spelling() c.sourceFile = fileName diff --git a/data/coerce.go b/data/coerce.go index 1834d503..a7b3ae37 100644 --- a/data/coerce.go +++ b/data/coerce.go @@ -163,7 +163,7 @@ func coerceFloat64(v interface{}) (interface{}, error) { case string: st, err := strconv.ParseFloat(value, 64) if err != nil { - return nil, err + return nil, errors.ErrInvalidFloatValue.Context(value) } return st, nil @@ -188,6 +188,10 @@ func coerceFloat32(v interface{}) (interface{}, error) { return float32(value), nil case int32: + if math.Abs(float64(value)) > math.MaxFloat32 { + return nil, errors.ErrLossOfPrecision.Context(value) + } + return float32(value), nil case int: @@ -200,12 +204,16 @@ func coerceFloat32(v interface{}) (interface{}, error) { return value, nil case float64: + if math.Abs(value) > math.MaxFloat32 { + return nil, errors.ErrLossOfPrecision.Context(value) + } + return float32(value), nil case string: st, err := strconv.ParseFloat(value, 32) if err != nil { - return nil, err + return nil, errors.ErrInvalidFloatValue.Context(value) } return float32(st), nil @@ -239,20 +247,18 @@ func coerceToInt(v interface{}) (interface{}, error) { return value, nil case float32: - r := int(value) - if float64(r) != math.Floor(float64(value)) { + if math.Abs(float64(value)) > math.MaxInt { return nil, errors.ErrLossOfPrecision.Context(value) } - return r, nil + return int(value), nil case float64: - r := int(value) - if float64(r) != math.Floor(value) { + if math.Abs(value) > math.MaxInt { return nil, errors.ErrLossOfPrecision.Context(value) } - return r, nil + return int(value), nil case string: if value == "" { @@ -339,20 +345,28 @@ func coerceInt32(v interface{}) (interface{}, error) { return int32(0), nil case int: - result := int32(value & math.MaxInt32) - if int(result) != value { + n := value + if n < 0 { + n = -n + } + + if n > math.MaxInt32 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return int32(value), nil case int64: - result := int32(value & math.MaxInt32) - if (value & math.MaxInt32) != int64(result) { + n := value + if n < 0 { + n = -n + } + + if n > math.MaxInt32 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return int32(value), nil case int32: return value, nil @@ -361,15 +375,18 @@ func coerceInt32(v interface{}) (interface{}, error) { return int32(value), nil case float32: + if math.Abs(float64(value)) > math.MaxInt32 { + return nil, errors.ErrLossOfPrecision.Context(value) + } + return int32(value), nil case float64: - result := int32(value) - if float64(result) != math.Floor(value) { + if math.Abs(float64(value)) > math.MaxInt32 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return int32(value), nil case string: if value == "" { @@ -403,43 +420,40 @@ func coerceToByte(v interface{}) (interface{}, error) { return value, nil case int: - result := byte(value & math.MaxInt8) - if int(result) != value { + if value < 0 || value > 255 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return byte(value), nil case int32: - result := byte(value & math.MaxInt8) - if (value & math.MaxInt8) != int32(result) { + if value < 0 || value > 255 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return byte(value), nil case int64: - result := byte(value & math.MaxInt8) - if (value & math.MaxInt8) != int64(result) { + if value < 0 || value > 255 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return byte(value), nil case float32: - result := byte(value) - if float64(result) != math.Floor(float64(value)) { + if value < 0.0 || value > 255.0 { return nil, errors.ErrLossOfPrecision.Context(value) } - return result, nil + return byte(value), nil case float64: - result := byte(value) - if float64(result) != math.Floor(value) { + if value < 0.0 || value > 255.0 { return nil, errors.ErrLossOfPrecision.Context(value) } + result := byte(value) + return result, nil case string: diff --git a/tests/datamodel/conversion_errors.ego b/tests/datamodel/conversion_errors.ego new file mode 100644 index 00000000..129fdce0 --- /dev/null +++ b/tests/datamodel/conversion_errors.ego @@ -0,0 +1,52 @@ +@test "datamodel: loss of precision or conversion errors" +{ + try { + a := byte(256) + @fail "byte() failed to catch loss of precision" + } catch (e) {} + + try { + a := byte(-2) + @fail "byte() failed to catch loss of precision" + } catch (e) {} + + try { + a := int(1e30) + @fail "int() failed to catch loss of precision" + } catch (e) {} + + try { + a := int32(2e11) + @fail "int32() failed to catch loss of precision" + } catch (e) {} + + try { + a := float32(3.4e39) + @fail "int32() failed to catch loss of precision" + } catch (e) {} + + try { + a := int("bob") + @fail "string() failed to catch invalid value" + } catch (e) {} + + try { + a := byte("bob") + @fail "string() failed to catch invalid value" + } catch (e) {} + + try { + a := int32("bob") + @fail "string() failed to catch invalid value" + } catch (e) {} + + try { + a := float32("bob") + @fail "string() failed to catch invalid value" + } catch (e) {} + + try { + a := float64("bob") + @fail "string() failed to catch invalid value" + } catch (e) {} +} \ No newline at end of file diff --git a/tools/buildver.txt b/tools/buildver.txt index 3a14987b..5bb385f3 100644 --- a/tools/buildver.txt +++ b/tools/buildver.txt @@ -1 +1 @@ -1.5-1164 +1.5-1165