Skip to content
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

Internal BitRotateL/R functions (and their aliases) return incorrect results when first argument is negative. #348

Closed
osir3z opened this issue Mar 22, 2023 · 2 comments

Comments

@osir3z
Copy link

osir3z commented Mar 22, 2023

Examples

BitRotateL($80000000, 4) returns -8, i.e. $FFFFFFF8, but it should return $00000008.
BitRotateR($80000000, 4) returns -134217728, i.e. $F8000000, but it should return $08000000.

Cause

Bit rotation is simulated by OR-ing together a left and a right bit shift.

static int a_rol(int value, int shift) {
if ((shift &= sizeof(value)*8 - 1) == 0)
return value;
return (value << shift) | (value >> (sizeof(value)*8 - shift));
}
static int a_ror(int value, int shift) {
if ((shift &= sizeof(value)*8 - 1) == 0)
return value;
return (value >> shift) | (value << (sizeof(value)*8 - shift));
}

But since the inputs are signed the right shifts are treated as arithmetic and are extending the sign bit.

Solution

The right shifts, on lines 700 and 706 above, just need to be recast similarly to the BitRShiftL function.

AVSValue BitRShiftL(AVSValue args, void*, IScriptEnvironment* ) { return int(unsigned(args[0].AsInt()) >> unsigned(args[1].AsInt())); }

@pinterf
Copy link

pinterf commented Mar 22, 2023

Good catch, in Avisynth 2.6 a_ror and a_rol was implemented only in assembly code, see below.
Probably when Avisynth+ project replaced asm code with C, they failed with this conversion.

Old one:

int __declspec(naked) __stdcall a_rol(int arg1, int arg2) { // asm rol r/m32, CL
    __asm {
        mov  eax, [esp+4]
        mov  ecx, [esp+8]
        rol  eax, cl
        ret  8
    }
}

@osir3z
Copy link
Author

osir3z commented Mar 22, 2023

Yeah, I noticed that while I was checking to see how long the current code has been live. Turns out it's been 10 years. I suppose that just shows how often people use bit rotation in their scripts.

Honestly, I was debugging my script for a few hours before I bothered to check the result of the rotation. And even once I knew it was wrong, I kept wondering if it was me that was wrong.

Anyway, I went down the rabbit hole and found the offending code, but I also looked at the definitions for the C++ bit shift operators. It turns out that, prior to the C++20 specification, right shifts on negative lhs is implementation defined (see https://en.cppreference.com/w/cpp/language/operator_arithmetic). Most implementations perform arithmetic shifts, but not all.

My point in mentioning this is that if you really want to be strict and ensure cross-compiler/platform compatibility, it might be worth casting all lhs' in the shift/rotate operations as unsigned, and sanitizing the rhs. Just a thought.

Keep up the excellent work. You folks are amazing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants