Skip to content

Commit

Permalink
fix: better loss-of-precision tests for some conversions
Browse files Browse the repository at this point in the history
Also, fixed @fail so it was useful in a test run without crashing
the test suite.
  • Loading branch information
tucats committed Dec 20, 2024
1 parent b0c4703 commit c75e7d4
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 37 deletions.
23 changes: 23 additions & 0 deletions bytecode/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bytecode

import (
"github.com/tucats/ego/data"
"github.com/tucats/ego/errors"
)

Expand Down Expand Up @@ -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)))
}
6 changes: 6 additions & 0 deletions bytecode/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const (
Say
Serialize
SetThis
Signal
StackCheck
StaticTyping
Store
Expand All @@ -135,6 +136,7 @@ const (
Template
Timer
TryPop
TryFlush
TypeOf
UnWrap
Wait
Expand Down Expand Up @@ -248,6 +250,7 @@ var opcodeNames = map[Opcode]string{
Say: "Say",
Serialize: "Serialize",
SetThis: "SetThis",
Signal: "Signal",
StackCheck: "StackCheck",
StaticTyping: "StaticTyping",
Stop: "Stop",
Expand All @@ -269,6 +272,7 @@ var opcodeNames = map[Opcode]string{
Timer: "Timer",
Try: "Try",
TryPop: "TryPop",
TryFlush: "TryFlush",
TypeOf: "TypeOf",
UnWrap: "UnWrap",
Wait: "Wait",
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions bytecode/try.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 5 additions & 5 deletions compiler/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -292,16 +292,16 @@ 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
}
} else {
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
}
Expand Down Expand Up @@ -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
Expand Down
76 changes: 45 additions & 31 deletions data/coerce.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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 == "" {
Expand Down Expand Up @@ -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
Expand All @@ -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 == "" {
Expand Down Expand Up @@ -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:
Expand Down
52 changes: 52 additions & 0 deletions tests/datamodel/conversion_errors.ego
Original file line number Diff line number Diff line change
@@ -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) {}
}
2 changes: 1 addition & 1 deletion tools/buildver.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5-1164
1.5-1165

0 comments on commit c75e7d4

Please sign in to comment.