tcsh-org / tcsh

This is a read-only mirror of the tcsh code repository.
https://www.tcsh.org/
Other
229 stars 42 forks source link

Possible overflow issue on expressions. #101

Open Krush206 opened 2 months ago

Krush206 commented 2 months ago

I noticed putn1 breaks the conversion if the last bit is set.

On my machine (an ARM-based cellphone)

@ calc = ( 1 << 63 )

raises a @: Badly formed number. error. I inspected why's that, and turns out the conversion yields a ( (left parenthesis) character.

I can tell the program isn't careful on overflow, since none of the variables and functions are typed unsigned, and there doesn't seem to exist any validity checks on overflow.

Typing putn1's argument as unsigned remedied the issue.

I'm not sure if my solution is portable, but is what made the fix. I also provide portability among different character encodings.

Lastly, line 193 on sh.exp.c has a wrong type. On my machine

@ ret = ( 0 || 1 << 32 )

yields 0.

zoulasc commented 2 months ago

Well you are just making the number unsigned which means that now negative expressions don't work anymore, with the tradeoff of doubling the positive number range... I don't think this is a useful change (negative expressions are more popular). It would be better to detect and fix: [DING!] 201>@ x = (1 << 65 ) [1:00pm] 202>echo $x 2

:-)

Krush206 commented 2 months ago

Maybe I wasn't clear. The concern isn't actually on overflow, but on that signed overflow is undefined behavior. According to this page, the standard states unsigned overflow wraps around, and is portable, unless strong optimizations are set.

However, I'm afraid the operation

if (n & ~(~(unsigned tcsh_number_t) 0 >> 1))

isn't portable, since it's bitwise. I'm (very likely wrongly) assuming the last bit is the sign bit on every machine. I don't know what the standard states on bitwise operations and sign bit.

I had a problem with relational operators, but

switch ((int) i) {
    tcsh_number_t is;

case GTR:
    if ((i = egetn(p1)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        is = -(tcsh_number_t) -i;
    else
        is = i;
    if ((i = egetn(p2)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        i = is > -(tcsh_number_t) -i;
    else
        i = is > (tcsh_number_t) i;
    break;

case GTR | 1:
    if ((i = egetn(p1)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        is = -(tcsh_number_t) -i;
    else
        is = i;
    if ((i = egetn(p2)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        i = is >= -(tcsh_number_t) -i;
    else
        i = is >= (tcsh_number_t) i;
    break;

case LSS:
    if ((i = egetn(p1)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        is = -(tcsh_number_t) -i;
    else
        is = i;
    if ((i = egetn(p2)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        i = is < -(tcsh_number_t) -i;
    else
        i = is < (tcsh_number_t) i;
    break;

case LSS | 1:
    if ((i = egetn(p1)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        is = -(tcsh_number_t) -i;
    else
        is = i;
    if ((i = egetn(p2)) &
~(~(unsigned tcsh_number_t) 0 >> 1))
        i = is <= -(tcsh_number_t) -i;
    else
        i = is <= (tcsh_number_t) i;
    break;
}

made the fix. Note I cast operands that can be represented in signed quantity on comparison because, from what I've read, the standard states comparison between signed and unsigned, before the comparison, the signed operand is converted to unsigned. I had problems without a cast.

Negative expressions are working fine for me, though I've tested only on one machine.