Skip to content

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jepler
Copy link
Contributor

@jepler jepler commented Jul 19, 2025

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

CPython throws ValueError in this case, while the code in this PR throws ZeroDivisionError.

Copy link

github-actions bot commented Jul 19, 2025

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:   +16 +0.002% standard
      stm32:   +36 +0.009% PYBV10
     mimxrt:   +24 +0.006% TEENSY40
        rp2:   +32 +0.003% RPI_PICO_W
       samd:   +40 +0.015% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:   +35 +0.008% VIRT_RV32

Copy link

codecov bot commented Jul 19, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 98.41%. Comparing base (e993f53) to head (f7d26f1).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #17716   +/-   ##
=======================================
  Coverage   98.41%   98.41%           
=======================================
  Files         171      171           
  Lines       22210    22211    +1     
=======================================
+ Hits        21857    21858    +1     
  Misses        353      353           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Jul 20, 2025
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"));
Copy link
Contributor

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(?

Copy link
Contributor Author

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)) {
Copy link
Contributor

@AJMansfield AJMansfield Jul 21, 2025

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

Copy link
Contributor Author

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.

py/objint_mpz.c Outdated
Comment on lines 359 to 360
} 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)) {
Copy link
Contributor

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.

Copy link
Contributor Author

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"));
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thanks.

@jepler jepler force-pushed the pow3-assert-fail branch from 4c4582b to d213a53 Compare July 21, 2025 14:55
This finding is based on fuzzing micropython. I manually minimized the
test case it provided.

Signed-off-by: Jeff Epler <jepler@gmail.com>
@jepler jepler force-pushed the pow3-assert-fail branch from d213a53 to f7d26f1 Compare July 21, 2025 15:17
Copy link
Contributor

@AJMansfield AJMansfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! The tests seem adequate for the change, and I've confirmed sensitivity with unix-standard: tests correctly fail on b12af7d and pass on 902411a.

Unless someone else has a concern, this PR is ready to merge from f7d26f1.

@jepler
Copy link
Contributor Author

jepler commented Jul 22, 2025

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.

@dpgeorge
Copy link
Member

I ran the rv32 tests locally with this PR, and did not get any failure. That basics_builtin_pow3_intbig test takes only a few seconds to run. I'm using newlib though, so maybe that's the difference?

@AJMansfield
Copy link
Contributor

I'll see if I can corroborate whether or not it works on my Pico2 in Hazard3 mode.

@AJMansfield
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
py-core Relates to py/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy