-
-
Notifications
You must be signed in to change notification settings - Fork 417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
integer divide by zero
detection broken on master branch
#277
Comments
I can confirm that. As of commit To me it`s not obvious what the problem is. |
This is because of the change: now "/" operation always returns a float. (same as pow ** or ^ also always returns float). This was done to not confuse non-programmers: why |
I am the author of that commit and I think the problem is sort of type changes as Antos said. Can you show me where I need to do apply fix? I can submit a PR if it's a low-hanging fruit. We also need to cover this case by a unit-test. |
FTR While |
I think here: https://github.com/antonmedv/expr/blob/6f43069eb7b4982627cec6072b974f4cdb99066a/optimizer/fold.go#L66-L78 Maybe we should check both of |
Personally, I think it has been a mistake to force the user into floating point arithmetic for division. If infinity is a valid value in division, why should |
Fixed constant folding for float/ints: b26d4b7 I'm not sure what is the best approach here. For "normal" people (managers, etc), it seems reasonable that 1 / 2 is equals to 0.5. |
Thanks for such a prompt action! 🚀
Why you think |
This is how int behaves. Not a mistake from the programmer's perspective. But kinda a mistake from user's perspective. |
For example in https://julialang.org/ |
I noticed you just changed the following test: https://github.com/antonmedv/expr/blob/b26d4b7339ed214b829ec0a22670941702e59828/expr_test.go#L1388-L1389 But I didn't expect this. Shouldn't |
As of |
Hey @antonmedv, we already resolved this issue, but the new behavior still seems a bit wrong to me. And I have some concerns about it: For example, if both of
I have been struggling to provide a nice and clean UX in current situation; and thinking some possible workaround methods to tackle this problem. One of possible workaround is: Check if In my use case, I print out a value to explain why a given expression is failed. Eventually, Wdyt? |
Well, 0 / 0 is NaN in floats arithmetics. This is by design. We should stick to IEEE 754. Is case we want to return an error, simply add a check in OpDevide itself =) |
Hmmm, I see. Thanks!
Can you please clarify this a bit, you mean I should pass a custom patch function to my expression or submit a PR to add this check at switch case? |
Right now only option is to replace / to a func call via Patch. |
Can you please share a code snippet, how can I tackle with this problem? I struggled here: type divPatcher struct{}
func (p *divPatcher) Visit(node *ast.Node) {
n, ok := (*node).(*ast.BinaryNode)
if !ok {
return
}
if n.Operator == "/" {
// TODO: Check if the right operand is a _number_ with value 0.
// If so, expr.Compile will return an error.
}
} |
Isn't the code here a good starting point to write that custom patch function? If you still have problems, I will take a look at it again later from my machine. |
Thanks for the ref. Patch funcs seems easy to write but the problem is |
During the patch you don’t know if var a zero. You need to replace / with a func call. In a func call throw an error. |
I created divFn := func(left, right float64) float64 {
if right == 0 {
panic("integer divide by zero")
}
return left / right
}
ast.Patch(node, &ast.CallNode{
// Callee: ???,
Arguments: []ast.Node{n.Left, n.Right},
}) I don't want to pass like |
Yes, right now it’s the only option to pass a func from env. We can add something to CallNode, maybe func field. |
Created a separate issue to discuss further about this: #287 Maybe I can get to it if you enlight my way. |
Hi @antonmedv, thank you for adding
But what I'm trying to accomplish is to return an error instead of evaluate those. As you said already, IEEE 754 mentions that diving to zero for floats are not error. But I'm mostly working with integer values. So in my case, it resulting a bug.
package main
import (
"fmt"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
)
func main() {
env := map[string]uint64{
"foo": 7,
"bar": 0,
}
tests := []struct {
input string
}{
{
input: "(foo / bar) > 10",
},
{
input: "10 > (foo / bar)",
},
{
input: "10 + 20 + 30 < (foo / bar)",
},
{
input: "10 + 20 + 30 > (foo / bar) - 40",
},
{
input: "10 > (foo / bar) + 20 + 30 + 40 + 50",
},
}
for _, t := range tests {
program, err := expr.Compile(t.input, expr.Env(env))
if err != nil {
panic(err)
}
// Patch
if n, ok := (program.Node).(*ast.BinaryNode); ok {
div := &ast.Function{
Name: "integer divide by zero",
Func: func(args ...any) (any, error) {
// TODO
return nil, nil
},
//Fast: nil,
//Types: nil,
//Validate: nil,
//Predicate: false,
}
ast.Patch(&program.Node, &ast.CallNode{
Func: div,
Arguments: []ast.Node{n.Left, n.Right},
})
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
} Could you please enlighten my way here? What do put in |
Here is the patch doc: https://expr.medv.io/docs/Visitor-and-Patch
|
Thanks, I checked and I'm not sure if it covers my scenario. Tried this one but it does not throw any error: package main
import (
"fmt"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
)
func main() {
env := map[string]uint64{
"foo": 7,
"bar": 0,
}
tests := []struct {
input string
}{
{
input: "(foo / bar) > 10",
},
{
input: "10 > (foo / bar)",
},
{
input: "10 + 20 + 30 < (foo / bar)",
},
{
input: "10 + 20 + 30 > (foo / bar) - 40",
},
{
input: "10 > (foo / bar) + 20 + 30 + 40 + 50",
},
}
for _, t := range tests {
program, err := expr.Compile(t.input, expr.Env(env))
if err != nil {
panic(err)
}
// Patch
if n, ok := (program.Node).(*ast.BinaryNode); ok {
div := &ast.Function{
Name: "integer divide by zero",
Func: func(args ...any) (any, error) {
// TODO: how to handle if len(args) != 2?
v1, ok1 := args[0].(uint64)
if !ok1 {
return nil, fmt.Errorf("first argument is not integer")
}
v2, ok2 := args[1].(uint64)
if !ok2 {
return nil, fmt.Errorf("second argument is not integer")
}
if v2 == 0 {
return nil, fmt.Errorf("integer divide by zero")
}
return v1 / v2, nil
},
}
ast.Patch(&program.Node, &ast.CallNode{
Func: div,
Arguments: []ast.Node{n.Left, n.Right},
})
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
} Prints:
My some questions here:
Thanks. |
Tried package main
import (
"fmt"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/ast"
)
func main() {
env := map[string]uint64{
"foo": 7,
"bar": 0,
}
tests := []struct {
input string
}{
{
input: "(foo / bar) > 10",
},
{
input: "10 > (foo / bar)",
},
{
input: "10 + 20 + 30 < (foo / bar)",
},
{
input: "10 + 20 + 30 > (foo / bar) - 40",
},
{
input: "10 > (foo / bar) + 20 + 30 + 40 + 50",
},
}
for _, t := range tests {
program, err := expr.Compile(t.input, expr.Env(env), expr.Patch(&visitor{}))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
}
type visitor struct{}
func (v *visitor) Visit(node *ast.Node) {
if n, ok := (*node).(*ast.BinaryNode); ok {
div := &ast.Function{
Name: "integer divide by zero",
Func: func(args ...any) (any, error) {
// TODO: how to handle if len(args) != 2?
v1, ok1 := args[0].(uint64)
if !ok1 {
return nil, fmt.Errorf("first argument is not integer")
}
v2, ok2 := args[1].(uint64)
if !ok2 {
return nil, fmt.Errorf("second argument is not integer")
}
if v2 == 0 {
return nil, fmt.Errorf("integer divide by zero")
}
return v1 / v2, nil
},
}
ast.Patch(node, &ast.CallNode{
Func: div,
Arguments: []ast.Node{n.Left, n.Right},
})
}
} |
Your first example is incorrect. It will not work. Visitor with Patch is the correct way. |
I couldn't figure it out what to put into func (v *visitor) Visit(node *ast.Node) {
n, ok := (*node).(*ast.BinaryNode)
if !ok {
return
}
lid, lok := n.Left.(*ast.IdentifierNode)
rid, rok := n.Right.(*ast.IdentifierNode)
switch {
case lok && rok && n.Operator == "/":
ast.Patch(node, &ast.CallNode{
Callee: &ast.IdentifierNode{Value: "_div", Method: true},
Func: &ast.Function{
Name: "_div",
Func: func(args ...any) (any, error) {
return -1, nil
},
},
Arguments: []ast.Node{lid, rid},
})
}
} |
You can also just try: https://expr.medv.io/docs/Operator-Overloading |
Thanks! I didn't pass But adding this dummy function really necessary? Is there any other way without including a function in |
Yeap, it is possible via |
I just updated the
expr
to latest commit on@master
and one of the my unit test is failed. Here is a simple, reproducible code snippet:I was expecting the following error:
Works as expected:
v1.9.0
Broken (tested at):
v1.9.1-0.20221106120435-3d4c21954310
If
bar
is0
in the following expression:(foo / bar) < 10
, should be resulting an error.The text was updated successfully, but these errors were encountered: