-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
objint_mpz: Fix pow3(1,2,0). #17716
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
base: master
Are you sure you want to change the base?
objint_mpz: Fix pow3(1,2,0). #17716
Conversation
Code size report:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #17716 +/- ##
=======================================
Coverage 98.38% 98.38%
=======================================
Files 171 171
Lines 22239 22240 +1
=======================================
+ Hits 21880 21881 +1
Misses 359 359 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
py/objint_mpz.c
Outdated
@@ -364,6 +364,9 @@ mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { | |||
mpz_t *lhs = mp_mpz_for_int(base, &l_temp); | |||
mpz_t *rhs = mp_mpz_for_int(exponent, &r_temp); | |||
mpz_t *mod = mp_mpz_for_int(modulus, &m_temp); | |||
if (mpz_is_zero(mod)) { | |||
mp_raise_msg(&mp_type_ZeroDivisionError, MP_ERROR_TEXT("divide by zero")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If CPython raises a ValueError
here, why mp_raise_msg(&mp_type_ZeroDivisionError
instead of just mp_raise_ValueError(
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it, including introducing a new MP_ERROR_TEXT.
py/objint_mpz.c
Outdated
@@ -364,6 +364,9 @@ mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { | |||
mpz_t *lhs = mp_mpz_for_int(base, &l_temp); | |||
mpz_t *rhs = mp_mpz_for_int(exponent, &r_temp); | |||
mpz_t *mod = mp_mpz_for_int(modulus, &m_temp); | |||
if (mpz_is_zero(mod)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be checked right after the typeerror check, rather than after it (potentially) already allocated memory for the mpz buffers for its arguments.
Note that integer 0 is never a concrete object when represented as a mp_obj_t
, it's always a small int --- so the condition to check for zero is just mp_obj_is_small_int(modulus) && MP_OBJ_SMALL_INT_VALUE(modulus) == 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, done.
Thinking about this led to the discovery of #17730.
8b4de89
to
4c4582b
Compare
py/objint_mpz.c
Outdated
} else { | ||
mp_obj_t result = mp_obj_new_int_from_ull(0); // Use the _from_ull version as this forces an mpz int | ||
mp_obj_int_t *res_p = (mp_obj_int_t *)MP_OBJ_TO_PTR(result); | ||
if (modulus == MP_OBJ_NEW_SMALL_INT(0)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
De-nest this so it's parallel as an else if
with the line above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
py/objint_mpz.c
Outdated
mp_obj_t result = mp_obj_new_int_from_ull(0); // Use the _from_ull version as this forces an mpz int | ||
mp_obj_int_t *res_p = (mp_obj_int_t *)MP_OBJ_TO_PTR(result); | ||
if (modulus == MP_OBJ_NEW_SMALL_INT(0)) { | ||
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("pow() 3rd argument cannot be 0")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use mp_raise_ValueError
for smaller code size, that's the whole reason those shortcut raise methods exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, thanks.
d213a53
to
f7d26f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something must be wrong with the change, rv32 failed (timeout) specifically on basics_builtin_pow3_intbig. I can't figure why, and I failed at performing the rv32 build locally on my debian bookworm system to reproduce it here. |
I ran the rv32 tests locally with this PR, and did not get any failure. That |
I'll see if I can corroborate whether or not it works on my Pico2 in Hazard3 mode. |
Can confirm that the tests pass on my Pico2 running in Hazard3 mode, not sure what the cause for the failure in qemu rv32 might be. |
I suggest rebasing this on latest master to see if it fixes the CI. |
Done. |
CI now passing. |
py/objint_mpz.c
Outdated
@@ -356,9 +356,10 @@ static mpz_t *mp_mpz_for_int(mp_obj_t arg, mpz_t *temp) { | |||
mp_obj_t mp_obj_int_pow3(mp_obj_t base, mp_obj_t exponent, mp_obj_t modulus) { | |||
if (!mp_obj_is_int(base) || !mp_obj_is_int(exponent) || !mp_obj_is_int(modulus)) { | |||
mp_raise_TypeError(MP_ERROR_TEXT("pow() with 3 arguments requires integers")); | |||
} else if (modulus == MP_OBJ_NEW_SMALL_INT(0)) { | |||
mp_raise_ValueError(MP_ERROR_TEXT("pow() 3rd argument cannot be 0")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new error string adds quite a bit of code size, for arguably not much gain.
Suggest making it smaller, maybe reuse the divide-by-zero message "divide by zero"
but keep it as a ValueError?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: divide by zero
>>> pow(1,1,0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: divide by zero
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That's a much more acceptable code-size increase now.
This finding is based on fuzzing micropython. I manually minimized the test case it provided. Signed-off-by: Jeff Epler <jepler@gmail.com>
Summary
This finding is based on fuzzing micropython. I manually minimized the test case it provided.
Testing
A fuzzer found a crash in 3-arg pow where the 3rd argument was zero. I added a test case for it, and fixed it.
Trade-offs and Alternatives
I initially wanted to throw ZeroDivisionError and re-use the error string but I now throw ValueError with a string that matches CPython (3.11.2)