Community discussions

MikroTik App
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

`conditional not boolean` bug with :onerror (new in 7.13)

Sun Jan 07, 2024 11:38 pm

The variable used to write the error message into in the :onerror global command (new in 7.13) is not scoped to the in={} and do={} of the :onerror like the loop variable of a :foreach loop is. This is is not the behavior I expected and it caused me some issues before I figured it out.

Any idea if this is a bug or just undocumented behavior? It's just me expecting the wrong thing.

Script demonstrating the problem:
:put "#### Starting :onerror Tests ####\n"

:put "## Test 2: Error in scope with and before no error"
{
    :put "# Test 2 part 1: erroring :onerror"
    :put "before :onerror \$ErrVar: $ErrVar"
    :onerror ErrVar in={
        :put "inside :onerror in={} \$ErrVar: $ErrVar"
        :error "error msg 2p1"
    } do={
        put "inside :onerror do={} \$ErrVar: $ErrVar"
    }
    :put "Post :onerror in scope \$ErrVar: $ErrVar"

    :put "\n# Test 2 part 2: not erroring :onerror"
    :put "before :onerror \$ErrVar: $ErrVar"
    :onerror ErrVar in={
        :put "inside :onerror in={} \$ErrVar: $ErrVar"
        #:put "msg 2p2"
    } do={
        put "inside :onerror do={} \$ErrVar: $ErrVar"
    }
    :put "Post :onerror in scope \$ErrVar: $ErrVar"
}

:put "\n### This demonstrates that \$ErrVar used in the :onerror is not scoped to the in={} and do={} of the :onerror."
:put "### This is not the behavior that I would expect, and isn't documented."

:put "\n#### Ended :onerror Tests ####"
Output of script:
#### Starting :onerror Tests ####

## Test 2: Error in scope with and before no error
# Test 2 part 1: erroring :onerror
before :onerror $ErrVar: 
inside :onerror in={} $ErrVar: 
inside :onerror do={} $ErrVar: error msg 2p1
Post :onerror in scope $ErrVar: error msg 2p1

# Test 2 part 2: not erroring :onerror
before :onerror $ErrVar: error msg 2p1
inside :onerror in={} $ErrVar: error msg 2p1
inside :onerror do={} $ErrVar: conditional not boolean
Post :onerror in scope $ErrVar: conditional not boolean

### This demonstrates that $ErrVar used in the :onerror is not scoped to the in={} and do={} of the :onerror.
### This is not the behavior that I would expect, and isn't documented.

#### Ended :onerror Tests ####

Script file loaded and executed successfully
I have also found another instance where I get the same "conditional not boolean" error message in the error variable of the :onerror that I can't specifically identify a cause of. It seem to be resolved by declaring and then erasing the (already empty) error variable. This looks like it is almost certainly caused by a bug.

Script snippet of where this bug occurs (these are some sloppy test for a log/print output function I wrote):
#########
# Tests #
#########

### For ErrOutFn ###
:put "\n#### Tests for ErrOutFn ####";
:global DEBUG true;

:put "## Input validation that should pass"

:foreach Fn in={$ErrOutFn} do={
    :put "";
    :put "### LOOP ITERATION";
    :put "";
    :put "## Input Validation Tests";
    :put "# Tests that should pass";

    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "warning" "SomeFn" "should not error";
        } do={
            :put "Should not show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"    

    :put "";
    :put "# Tests that should fail";

    {
        :onerror ErrVal in={
            $Fn ;
        } do={
            :put "Should show error (I provided no input): $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "info";
        } do={
            :put "Should show error (I provided only \$1): $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "info" "SomeFn";
        } do={
            :put "Should show error (I provided only \$1,\$2): $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "info" "SomeFn" "test 1" true;
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "warning" "SomeFn" "test 2" 1;
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "debug" "SomeFn" "test 3" "1";
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "warning" "SomeFn" "test 4, default exit (do exit)"
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    $Fn "error" "SomeFn" "test 5, should not exit" "false";

    {
        :onerror ErrVal in={
            $Fn "info" "SomeFn" "test 6, default exit (do exit)";
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "debug" "SomeFn" "test 7, default exit (do exit)";
        } do={
            :put "Should show error: $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"
}

:put "\$Fn: $Fn"

# I need to declare and reset $ErrVar otherwise I get a "condition is not boolean error:, probably a bug
#:local ErrVal;
#:set ErrVal;

### For DbgFn ###
:put "\n#### Tests for DbgFn ####";
:foreach Fn in={$DbgFn} do={
    :put "";
    :put "### LOOP ITERATION";
    :put "";
    :put "## Input Validation Tests";

    :put "\$ErrVal: $ErrVal"
    {
        :onerror ErrVal in={
            $Fn ;
        } do={
            :put "Should show error (I provided no input): $ErrVal";
            :put "";
        }
    }
    :put "\$ErrVal: $ErrVal"

    {
        :onerror ErrVal in={
            $Fn "SomeFn";
        } do={
            :put "Should show error (I provided only \$1): $ErrVal";
            :put "";
        }
    }
    # :local ErrVal;
    # :set ErrVal;

    :put "\$ErrVal: $ErrVal";

    :put "\$ErrVal data type: $[:typeof "$ErrVal"]";
}


:set DEBUG;
Output of script:
#### Tests for ErrOutFn ####
## Input validation that should pass

### LOOP ITERATION

## Input Validation Tests
# Tests that should pass
$ErrVal: 
**Dbg: warning,SomeFn: should not error
Should not show error: warning,SomeFn: should not error

$ErrVal: 

# Tests that should fail
warning: $OutBaseFn: previous log message had improperly specified severity $1 to this function should be: debug, info, warning, or error
**Dbg: invalid,: 
Should show error (I provided no input): invalid,: 

$ErrVal: 
**Dbg: info,: 
Should show error (I provided only $1): info,: 

$ErrVal: 
**Dbg: info,SomeFn: 
Should show error (I provided only $1,$2): info,SomeFn: 

$ErrVal: 
**Dbg: info,SomeFn: test 1
Should show error: info,SomeFn: test 1

$ErrVal: 
**Dbg: warning,SomeFn: test 2
Should show error: warning,SomeFn: test 2

$ErrVal: 
**Dbg: debug,SomeFn: test 3
Should show error: debug,SomeFn: test 3

$ErrVal: 
**Dbg: warning,SomeFn: test 4, default exit (do exit)
Should show error: warning,SomeFn: test 4, default exit (do exit)

$ErrVal: 
**Dbg: error,SomeFn: test 5, should not exit
**Dbg: info,SomeFn: test 6, default exit (do exit)
Should show error: info,SomeFn: test 6, default exit (do exit)

$ErrVal: 
**Dbg: debug,SomeFn: test 7, default exit (do exit)
Should show error: debug,SomeFn: test 7, default exit (do exit)

$ErrVal: 
$Fn: 

#### Tests for DbgFn ####

### LOOP ITERATION

## Input Validation Tests
$ErrVal: 
**Dbg: debug,: 
Should show error (I provided no input): conditional not boolean

$ErrVal: 
**Dbg: debug,SomeFn: 
Should show error (I provided only $1): conditional not boolean

$ErrVal: 
$ErrVal data type: nothing

Script file loaded and executed successfully
Correction: The first issue raised in here is not a bug, the 2nd issue looks like it is.
Last edited by ther33 on Wed Jan 10, 2024 12:27 am, edited 3 times in total.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12014
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Possible bugs with :onerror (new in 7.13)

Mon Jan 08, 2024 1:11 am

Any idea if this is a bug or just undocumented behavior?

The problem is not RouterOS, but what you expect.
If there is no error, the variable is not defined, so if you don't define it first, it's obvious that it's nothing.

:put [:local errorName "empty"; :onerror errorName in={:put "errorName is $errorName"; :error "something"} do={:put "errorName is $errorName"}]

result code

errorName is empty
errorName is something
true

Even the comparison with foreach is ridiculous.
It is obvious that what is used in foreach is already defined at the time of the loop.


And as for the rest, it's ridiculous to try to follow the steps you took if you don't also post the rest of the script.
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Mon Jan 08, 2024 1:23 am

Thanks for correcting me on my scoping expectations. I'm inexperienced so my expectations are not well justified.

I'm trying to use :onerror to write better log output when I get errors from functions called by my scripts, so the variable has been quite useful for me. It can act like sort of stderr stream.

rextended,
Here is a (complete) example script showing the 2nd issue. I suspect it's caused by the variable for the error message in :onerror somehow interacting across variable scopes. I don't understand how/why this would happen.

Script:
{
    :onerror ErrVal in={
        :put "msg 1"
        :error "error msg 1";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
}
:put ""

{
    :onerror ErrVal in={
        :put "msg 2";
        #:error "error msg 3"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    #:local ErrVal
}
:put ""

# Declaring any variable between an  seems to prevent the `conditional not boolean` error
:local AnyVariable

{
    :onerror ErrVal in={
        :put "msg 3";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
}
Output:
msg 1
do={} executed
inside :onerror do={} $ErrVal: error msg 1

msg 2
do={} executed
inside :onerror do={} $ErrVal: conditional not boolean

msg 3
Last edited by ther33 on Mon Jan 08, 2024 3:45 am, edited 3 times in total.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: Possible bugs with :onerror (new in 7.13)

Mon Jan 08, 2024 1:58 am

Any idea if this is a bug or just undocumented behavior?
The problem is not RouterOS, but what you expect.
Agreed. :onerror is not loop — so it has a different scope. The variable scoping behavior is consistent with C++/Java/JS/etc. exception handler (e.g. a "try/catch" block) IMO.

I'm sure there are use case for :onerror. It's main claim to fame is that the error text from the "do" block is available in the error handler. Otherwise, there likely better way to express some script than using :onerror.
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12014
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Possible bugs with :onerror (new in 7.13)

Mon Jan 08, 2024 9:36 am

There are situations where currently "on-error-resume-next" must be used because it is poorly managed by RouterOS, such as ":resolve",
for everything else, error-proof scripts must be written, preventing any errors that may occur, such as a missing parameter in a called function.
Simply the function must NOT cause errors,
internally managing "normalizing" the expected inputs rather than giving an error (accidentally or deliberately) if a parameter is not specified.
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 2:10 am

There are situations where currently "on-error-resume-next" must be used because it is poorly managed by RouterOS, such as ":resolve",
for everything else, error-proof scripts must be written, preventing any errors that may occur, such as a missing parameter in a called function.
Simply the function must NOT cause errors,
internally managing "normalizing" the expected inputs rather than giving an error (accidentally or deliberately) if a parameter is not specified.
Of course I want to do the best I can at input validation and prevent commands from outputting errors.

I have been writing functions which use :error to return a (terminating) error if my input validation fails. I then put the function call in the in={} block of the :onerror and log errors and/or take other remediating actions in the do={} block.

This allows my log messages to be prefixed by the script and function name, while retaining specificity, and allowing input validation to happen in the function (reducing replicated code).

Are you saying there are bugs related to error handling which make this approach not work?

Is the script example I provided in my 2nd post an example of one, or do I misunderstand how scoping works in RouterOS?
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 5:56 pm

I guess I'm not sure what you think is wrong. Getting the error text is main point of :onerror – so on the right track for adding prefix to logging text.

First understand only the in= block runs if there is NO error (either explicit :error, or RouterOS generated one). The "do=" block only run IF some error occurred. The following code works as expected, the local variables IN SAME SCOPE are available:
{ :local prefix "vardata"; :onerror err in={:error "errortest"} do={:put "$prefix $err"} }
# vardata errortest

But in general you can avoid most error conditions by using ":if" to AVOID error conditions in the first place – which is generally a better approach. If you do this, you can write whatever you want to log if your code since you check for bad values/etc BEFORE an error is generated.

Totally get that's not always possible... and where :onerror might be useful. But I'm not sure it good idea MORE work other than logging in the do= of :onerror... Perhaps wrapping your ENTIRE script in the in= block, and the do= block of the main :onerror does your needed logging and exits after is a better approach THAN using MULTIPLE onerror blocks throughout a script.
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 7:49 pm

Obviously I'm having a hard time explaining this.

I am using :onerror to write logs that are supposed to look something like this:
script,warning TestScript.rsc,$TestFn: input validation error: input not boolean
The actual error message is always generated by an :error command that is somewhere in the :if statements I'm using in my functions for input validation.

The log command in the do={} block of the :onerror that generates this log message looks something like this:
:log warning ($0 . "," . $ErrVal);
$0 is the prefix from the script
$ErrVal is the variable that is used to save the error in for the :onerror like: :onerror ErrVal in={} do={}


Little of that is relevant to the issue with :onerror that the example script in my 2nd post shows:

The example script in my 2nd post shows that even if I create explicit scopes that should be isolated for each :onerror, I cannot use an :onerror that shouldn't execute the do={} block after one that does with the same variable in both :onerror ($ErrVal in my examples). I get a `condition not boolean` error in the 2nd :onerror. That `conditional not boolean` error causes the do={} block of the 2nd :onerror to be executed, causing a false positive and breaking the error handling logic.

I found two workaround for this: I can declare $ErrVal in the scope that wraps the 2nd :onerror before using in in the the 2nd :onerror. I can also declare another variable in-between the scopes as is shown in my example script.

I'm fairly certain I understand :onerror and scopes in RouterOS enough to conclude that what I have found is a bug in the implementation of :onerror by MikroTik. I am posting about this because I want to get input from more knowledgeable people (like rextended and Amm0) before I conclude this is a bug. Also, if it's a bug, I want to make sure that other people writing RouterOS scripts can find info on it.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 8:00 pm

$0 is the prefix from the script
So to summarize, the bug is that $0 (script name) is not populated?
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 9:17 pm

$0 is the prefix from the script
So to summarize, the bug is that $0 (script name) is not populated?
No,
$0 is not related to the bug at all. Using $0 for the script name, declared with :local 0 "ScriptName.rsc" in the global scope of script, has always worked properly in my testing. I removed it from the example script in my 2nd post for clarity. I also used :put rather than :log to simplify it even further.

The variable related to the bug is $ErrVal.This is the variable that :onerror should write an error message the occurs in it's in={} block to. I am using it in each :onerror as follows:
:onerror ErrVal in={} do={}
$ErrVal for each of the 3 :onerror that I have in the example script in my 2nd post should be fully isolated. Each :onerror is enclosed by {}. The `conditional not boolean` error I get shows that they aren't and breaks the error handling.

If I replace $ErrVal with a different variable name for the 2nd :onerror, the 2nd :onerror doesn't falsely trigger the do={} block with the `conditional not boolean` error.

Correction: It appears that the $ErrVal not being isolated could cause the problem, but it isn't. Even if a different variable is used in the :onerror that isn't supposed to execute the do={} block, it still shows the bug behavior.
Last edited by ther33 on Tue Jan 09, 2024 11:14 pm, edited 1 time in total.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 9:36 pm

It's a matter of opinion on the "right" scoping rules. Mikrotik does not have some detailed spec for scripting language. ;)

Personally, I'd avoid all the variable "shadowing". For example, reassigning 0 in a local, where 0 is normally a global is not exactly "defined" – apparently it works that way.

You can always avoid "conditional not boolean" by checking :typeof to avoid needing any error handling. Basically, while :onerror is useful... not sure I'd design my script around it since IMO it really best to add more checks to AVOID any errors BEFORE they happen... And in most cases that's possible to do without needing :onerror.

Anyway, you can always report it to Mikrotik at support@mikrotik.com. But I'm not sure it really an "bug" per se – since there is not always any exact definition/rule for how these things work (other than experience).
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Tue Jan 09, 2024 10:55 pm

Yeah, the lack of detailed documentation or source code is frustrating. I don't want to have to write a whole bunch of test cases to understand how something works (or is broken). I've definitely done that here.

I modified my example script to better show how the bug I'm noting in :onerror is not just a design decision. It adds another :onerror with a different variable name rather than $ErrVal that fails in the same way. This makes is clearer that there is a real bug rather than undocumented scoping behavior. I also use {} in :if statements in a way that is explicitly documented. I also added additional print statements showing the value and type of $ErrVal, demonstrating as best I can that it doesn't persist across scopes.

There is no point in me investigating this further, I can't solve the problem anyways, MikroTik has to. I'll wait a bit to see if anyone else has something to add and then I will submit a report to MikroTik.

This bug can be worked around by declaring the variable used for :onerror before each :onerror (without assigning a value). If each :onerror isn't in an isolated scope then the variable should be unset after.

Expanded example script follows:
Script:
# :if is used to demonstrate that this bug happens regardless of how :onerror enclosed in {} is used
# the same bug occurs when the :if is removed and only the {} are left around the :onerror.

:if (true) do={  
    :put "## :onerror 1";
    :put "# should trigger do={} block"
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 1"
        :error "error msg 1";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"

:put ""

    :put "## :onerror 2 (uses different variable in same scope as 1, works properly)";
    #:put "\$ErrVar: $ErrVar";
    #:put "\$ErrVar data type: $[:typeof $ErrVar]";
    :onerror ErrVar in={
        :put "msg 2";
        #:error "error msg 2"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVar: $ErrVar";
    }
    :put "inside scope, after :onerror \$ErrVar: $ErrVar"

:put ""
}

:if (true) do={
    :put "## :onerror 3";
    :put "# should trigger do={} block"
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 3"
        :error "error msg 3";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={
    :put "## :onerror 4 ";
    :put "# Uses different var in separate, parallel, scope to 3. Should not execute do={} block, but does."
    :put "\$ErrVar: $ErrVar";
    :put "\$ErrVar data type: $[:typeof $ErrVar]";
    :onerror ErrVar in={
        :put "msg 4";
        #:error "error msg 4"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVar: $ErrVar";
    }
    :put "inside scope, after :onerror \$ErrVar: $ErrVar"
}

:put ""

:if (true) do={
    :put "## :onerror 5";
    :put "# Uses same var as 3 in separate, parallel, scope to 3. Should not execute do={} block, but does.";
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 5";
        #:error "error msg 5"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

# Declaring any variable between seems to prevent the `conditional not boolean` error as well
#:local AnyVariable

:if (true) do={
    :put "## :onerror 6";
    :put "# Uses same var as  3 in separate, parallel scope to 3, declares it in local scope. Works properly."
    :put "Declare \$ErrVal in local scope";
    :local ErrVal;
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 6";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}
Output:
## :onerror 1
# should trigger do={} block
$ErrVal: 
$ErrVal data type: nothing
msg 1
do={} executed
inside :onerror do={} $ErrVal: error msg 1
inside scope, after :onerror $ErrVal: error msg 1

## :onerror 2 (uses different variable in same scope as 1, works properly)
msg 2
inside scope, after :onerror $ErrVar: 

## :onerror 3
# should trigger do={} block
$ErrVal: 
$ErrVal data type: nothing
msg 3
do={} executed
inside :onerror do={} $ErrVal: error msg 3
inside scope, after :onerror $ErrVal: error msg 3

## :onerror 4 
# Uses different var in separate, parallel, scope to 3. Should not execute do={} block, but does.
$ErrVar: 
$ErrVar data type: nothing
msg 4
do={} executed
inside :onerror do={} $ErrVar: conditional not boolean
inside scope, after :onerror $ErrVar: conditional not boolean

## :onerror 5
# Uses same var as 3 in separate, parallel, scope to 3. Should not execute do={} block, but does.
$ErrVal: 
$ErrVal data type: nothing
msg 5
do={} executed
inside :onerror do={} $ErrVal: conditional not boolean
inside scope, after :onerror $ErrVal: conditional not boolean

## :onerror 6
# Uses same var as  3 in separate, parallel scope to 3, declares it in local scope. Works properly.
Declare $ErrVal in local scope
$ErrVal: 
$ErrVal data type: nothing
msg 6
inside scope, after :onerror $ErrVal: 

Hopefully this isn't excessively pedantic Amm0, but I'm not variable shadowing. $0 is defined only once per script, in the most global scope for that script. $0 is of course automatically assigned the function name inside a function, so it is convenient to use the same variable in both instances. Function scopes are entirely separate from script scopes. Only :global declared variables can be shared between both. Because of the special purpose of $0 in functions it's convenient to use the same variable for the script name too. It would be very unlikely to be used for any other purpose than the name of the function (or in my special case, the script).
Last edited by ther33 on Thu Jan 11, 2024 6:21 am, edited 1 time in total.
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: Bug with :onerror (new in 7.13)

Tue Jan 09, 2024 11:53 pm

This is also strange behaviour:
{
  :local urls ("https://httpstat.us/400", "https://httpstat.us/500", "https://httpstat.us/200", "https://httpstat.us/404")
  :local l [:len $urls]
  :local idx 0
  :local resp ""

  :while ($resp = "" && $idx < $l) do={
    :local url [:pick $urls $idx]

    :onerror err in={
      :set resp [/tool/fetch url=$url output=user as-value]
    } do={
      :put "Fetch error for url \"$url\", $err"
      :set idx ($idx + 1)
    }
  }

  :if ($resp != "") do={
    :put "Fetched: $($resp->"data")"
  }
}
Fetch error for url "https://httpstat.us/400", failure: Fetch failed with status 400
Fetch error for url "https://httpstat.us/500", failure: Fetch failed with status 500
Fetch error for url "https://httpstat.us/200", conditional not boolean
Fetched: 200 OK


and here is ok with :do {} on-error={}
{
  :local urls ("https://httpstat.us/400", "https://httpstat.us/500", "https://httpstat.us/200", "https://httpstat.us/404")
  :local l [:len $urls]
  :local idx 0
  :local resp ""

  :while ($resp = "" && $idx < $l) do={
    :local url [:pick $urls $idx]

    :do {
      :set resp [/tool/fetch url=$url output=user as-value]
    } on-error={
      :put "Fetch error for url \"$url\""
      :set idx ($idx + 1)
    }
  }

  :if ($resp != "") do={
    :put "Fetched: $($resp->"data")"
  }
}
Fetch error for url "https://httpstat.us/400"
Fetch error for url "https://httpstat.us/500"
Fetched: 200 OK
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Bug with :onerror (new in 7.13)

Wed Jan 10, 2024 12:22 am

Yes, that looks like the same bug that I found.

If I try the workaround that I suggested (declaring the variable before each :onerror), it works.

Some subtle changes for my debugging purposes:
Script:
{
  :local urls {"https://httpstat.us/400"; "https://httpstat.us/500"; "https://httpstat.us/200"};
  :put [:typeof $urls];
  :local l [:len $urls];
  :local idx 0;
  :local resp "";

  :while ($resp = "" && $idx < $l) do={
    :local url [:pick $urls $idx];
    :local err;
    :put "before each :onerror \$err: $err";
    :onerror err in={
      :set resp [/tool/fetch url=$url output=user as-value];

    } do={
      :put "Fetch error for url \"$url\", $err";
      :set idx ($idx + 1);
    }
    :put "after each :onerror \$err: $err";
    :put "";
  }

  :if ($resp != "") do={
    :put "Fetched: $($resp->"data")";
  }
}
Output:
array
before each :onerror $err: 
Fetch error for url "https://httpstat.us/400", failure: Fetch failed with status 400
after each :onerror $err: failure: Fetch failed with status 400

before each :onerror $err: 
Fetch error for url "https://httpstat.us/500", failure: Fetch failed with status 500
after each :onerror $err: failure: Fetch failed with status 500

before each :onerror $err: 
after each :onerror $err: 

Fetched: 200 OK
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: Bug with :onerror (new in 7.13)

Wed Jan 10, 2024 12:25 am

Seem to be it, clearly a bug.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: Bug with :onerror (new in 7.13)

Wed Jan 10, 2024 12:33 am

Okay, now I see where the "conditional not boolean" comes in. Agree a bit strange, it also check the return value of the in=. The ":set resp" does not return anything, so :onerror is somehow checking the return value from the in= block.

e.g.

:onerror err in={
:set resp [/tool/fetch url=$url output=user as-value]
} do={...}

vs

:onerror err in={
:set resp [/tool/fetch url=$url output=user as-value]
:return true
} do={...}
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Wed Jan 10, 2024 12:47 am

Or just
:return [:set resp [/tool/fetch url=$url output=user as-value]]
It just needs return, but it's not documented like that and imho it shuldn't expect return from this scope.
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Wed Jan 10, 2024 1:00 am

Good catch Amm0. I concur. It looks like as long as there is something returned, the bug isn't triggered.

It is interesting that $ErrVal ends up populated with the error message from the previous :onerror when there is a :return <any value> statement in the in={} block.

See the new version of my example script below:
Script:
# :if is used to demonstrate that this bug happens regardless of how :onerror enclosed in {} is used
# the same bug occurs when the :if is removed and only the {} are left around the :onerror.

:if (true) do={  
    :put "## :onerror, baseline, should and does not trigger do={} block";
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 1"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={  
    :put "## :onerror 1";
    :put "# should trigger do={} block"
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 1"
        :error "error msg 1";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"

:put ""

    :put "## :onerror 2 (uses different variable in same scope as 1, works properly)";
    #:put "\$ErrVar: $ErrVar";
    #:put "\$ErrVar data type: $[:typeof $ErrVar]";
    :onerror ErrVar in={
        :put "msg 2";
        #:error "error msg 2"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVar: $ErrVar";
    }
    :put "inside scope, after :onerror \$ErrVar: $ErrVar"

:put ""
}

:if (true) do={
    :put "## :onerror 3";
    :put "# should trigger do={} block"
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 3"
        :error "error msg 3";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={
    :put "## :onerror 4 ";
    :put "# Uses different var in separate, parallel, scope to 3. Should not execute do={} block, but does."
    :put "\$ErrVar: $ErrVar";
    :put "\$ErrVar data type: $[:typeof $ErrVar]";
    :onerror ErrVar in={
        :put "msg 4";
        #:error "error msg 4"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVar: $ErrVar";
    }
    :put "inside scope, after :onerror \$ErrVar: $ErrVar"
}

:put ""

:if (true) do={
    :put "## :onerror 5";
    :put "# Uses same var as 3 in separate, parallel, scope to 3. Should not execute do={} block, but does.";
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 5";
        #:error "error msg 5"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={
    :put "## :onerror 6";
    :put "# Uses same var as 3 in separate, parallel scope to 3, returns true from in={}. Works properly."
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 6";
        :return true
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={
    :put "## :onerror 7";
    :put "# Uses same var as 3 in separate, parallel scope to 3, returns false from in={}. Works properly."
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 7";
        :return false
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

:if (true) do={
    :put "## :onerror 8";
    :put "# Uses same var as  3 in separate, parallel scope to 3, returns \"some str\" from in={}. Works properly."
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 8";
        :return "some str"
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""

# Declaring any variable between seems to prevent the `conditional not boolean` error as well
#:local AnyVariable

:if (true) do={
    :put "## :onerror 9";
    :put "# Uses same var as  3 in separate, parallel scope to 3, declares it in local scope. Works properly."
    :put "Declare \$ErrVal in local scope";
    :local ErrVal;
    :put "\$ErrVal: $ErrVal";
    :put "\$ErrVal data type: $[:typeof $ErrVal]";
    :onerror ErrVal in={
        :put "msg 9";
    } do={
        :put "do={} executed";
        :put "inside :onerror do={} \$ErrVal: $ErrVal";
    }
    :put "inside scope, after :onerror \$ErrVal: $ErrVal"
}

:put ""
Output:
## :onerror, baseline, should and does not trigger do={} block
$ErrVal: 
$ErrVal data type: nothing
msg 1
inside scope, after :onerror $ErrVal: 

## :onerror 1
# should trigger do={} block
$ErrVal: 
$ErrVal data type: nothing
msg 1
do={} executed
inside :onerror do={} $ErrVal: error msg 1
inside scope, after :onerror $ErrVal: error msg 1

## :onerror 2 (uses different variable in same scope as 1, works properly)
msg 2
inside scope, after :onerror $ErrVar: 

## :onerror 3
# should trigger do={} block
$ErrVal: 
$ErrVal data type: nothing
msg 3
do={} executed
inside :onerror do={} $ErrVal: error msg 3
inside scope, after :onerror $ErrVal: error msg 3

## :onerror 4 
# Uses different var in separate, parallel, scope to 3. Should not execute do={} block, but does.
$ErrVar: 
$ErrVar data type: nothing
msg 4
do={} executed
inside :onerror do={} $ErrVar: conditional not boolean
inside scope, after :onerror $ErrVar: conditional not boolean

## :onerror 5
# Uses same var as 3 in separate, parallel, scope to 3. Should not execute do={} block, but does.
$ErrVal: 
$ErrVal data type: nothing
msg 5
do={} executed
inside :onerror do={} $ErrVal: conditional not boolean
inside scope, after :onerror $ErrVal: conditional not boolean

## :onerror 6
# Uses same var as 3 in separate, parallel scope to 3, returns true from in={}. Works properly.
$ErrVal: 
$ErrVal data type: nothing
msg 6
inside scope, after :onerror $ErrVal: conditional not boolean

## :onerror 7
# Uses same var as 3 in separate, parallel scope to 3, returns false from in={}. Works properly.
$ErrVal: 
$ErrVal data type: nothing
msg 7
inside scope, after :onerror $ErrVal: conditional not boolean

## :onerror 8
# Uses same var as  3 in separate, parallel scope to 3, returns "some str" from in={}. Works properly.
$ErrVal: 
$ErrVal data type: nothing
msg 8
inside scope, after :onerror $ErrVal: conditional not boolean

## :onerror 9
# Uses same var as  3 in separate, parallel scope to 3, declares it in local scope. Works properly.
Declare $ErrVal in local scope
$ErrVal: 
$ErrVal data type: nothing
msg 9
inside scope, after :onerror $ErrVal:
 
optio
Long time Member
Long time Member
Posts: 675
Joined: Mon Dec 26, 2022 2:57 pm

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Wed Jan 10, 2024 1:15 am

Regardless, maybe someone from MT has read this viewtopic.php?p=1000982#p1000982 and implemented :onerror :)
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 12014
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Possible bugs with :onerror (new in 7.13)

Wed Jan 10, 2024 11:03 am

This bug can be worked around by declaring the variable used for :onerror before each :onerror...

If I try the workaround that I suggested (declaring the variable before each :onerror), it works.

Did you figure it out!!!
I already answered you (and I suggest the correct way, not you) in post #2,
but you were too caught up in your Machiavellian examples to realize it...
viewtopic.php?t=203158#p1046867
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: Possible bugs with :onerror (new in 7.13)

Thu Jan 11, 2024 6:23 am

Did you figure it out!!!
I already answered you (and I suggest the correct way, not you) in post #2,
but you were too caught up in your Machiavellian examples to realize it...
viewtopic.php?t=203158#p1046867
The workaround I am suggesting is to declare `:local ErrVal;` but not to assign any value to it. This is only because it is convenient to declare the variable that is to be used, declaring a different variable also seems to accomplish the same thing. The variable used for the the :onerror does not need to be declared before the :onerror, but it does need to be empty in order to not lead to unpredictable results.

The variable declaration somehow impacts the :onerror and causes it to work properly. It shouldn't be necessary at all.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Thu Jan 11, 2024 8:08 pm

Or, just always :return something in the in= block... That seems to be the implied requirement...whether that's a bug or just undocumented is better left to Mikrotik. Did you report this to support@mikrotik.com?

One thing I noticed in your examples is using # comments in the in= block... That may actually be CAUSING the error condition (thus the do= block to run). At the CLI at least comment are not allowed inline like that... perhaps it's enforced in other contexts for :onerror, dunno?

The docs describe using :onerror in a :if statement and suggest :onerror itself returns a True or False value... so there are few things going on inside :onerror involving the call stack. e.g. :set return nil ([:nothing]) while :put BOTH returns it's input and writes to the screen.
> :put [:typeof [:set zzz 12]]                                    
nil
> :put [:typeof [:put "sometext"]]         
sometext
str
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Wed Jan 17, 2024 6:49 am

I tested it without comments. No change. I did report it to MIkroTik today.

I don't think that :local ErrVal to reset the variable used in :onerror before the :onerror would impact what the in={} block returns. So considering that workaround works, I don't think it's can be a simple as an implied requirement to return something.
 
ther33
just joined
Topic Author
Posts: 15
Joined: Sun Apr 02, 2023 11:58 pm

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Sat Jan 27, 2024 11:23 am

MIkrotik responded to the report saying that they are aware of the issue and are planning to improve behavior in future releases.

I'll be looking for that in future release notes.
 
User avatar
Amm0
Forum Guru
Forum Guru
Posts: 3509
Joined: Sun May 01, 2016 7:12 pm
Location: California

Re: `conditional not boolean` bug with :onerror (new in 7.13)

Sat Jan 27, 2024 7:08 pm

I don't think it's can be a simple as an implied requirement to return something.
Perhaps not. To be clear, I was NOT saying it should be a "requirement" ;)

FWIW, code blocks already have an implict return of the last value of cmd in block, so why I thought that was involved (like that a :set returns nil, but :put returns a string) e.g.
:global x [:do {:put "a"; :put "b"; :put "c"}]
:put "tostr: $[:tostr $x] len: $[:len $x] typeof: $[:typeof $x] array0: $($x->0) array1: $($x->1)"

tostr: ;c 
len: 2 
typeof: array 
array0:  
array1: c
As shown the "c" is returned in an 2 element array, even without a return. Why it's an array, or perhaps anything at all - mysteries of RouterOS script. But it's not a "nil" return, which seems to be involved in the bug here.

Who is online

Users browsing this forum: No registered users and 11 guests