Port, board and/or hardware
Unix port, Linux
MicroPython version
MicroPython v1.28.0-624-gb9cceaa6fd on 2026-07-24
Reproduction
class NoneLen:
def __len__(self):
return None
print(len(NoneLen()))
Expected behaviour
CPython raises (3.11.15):
TypeError: 'NoneType' object cannot be interpreted as an integer
Observed behaviour
MicroPython returns the value unchanged:
A str or float return is passed through the same way, and a negative int is returned as-is instead of raising ValueError: __len__() should return >= 0.
Additional Information
A negative __len__ flows unchecked into buffer-size arithmetic. bytearray(obj) and array.array(t, obj) carry the value through instance_unary_op into array_construct, which stores it as a size_t — a negative small int wraps to a huge value — and hands it to array_new:
|
mp_obj_t len_in = mp_obj_len_maybe(initializer); |
|
if (len_in == MP_OBJ_NULL) { |
|
len = 0; |
|
} else { |
|
len = MP_OBJ_SMALL_INT_VALUE(len_in); |
|
} |
|
|
|
mp_obj_array_t *array = array_new(typecode, len); |
|
o->items = m_new(byte, typecode_size * o->len); |
Concretely, bytearray(obj) with a __len__ returning -1 attempts to allocate SIZE_MAX bytes (MemoryError: ... allocating 18446744073709551615 bytes). Validating the result in instance_unary_op rejects it there. Related issues on the same unchecked-__len__ surface: #18617 (bytearray buffer overflow via a length/iterator mismatch) and #18620 (len * itemsize overflow).
Root Cause
instance_unary_op (py/objtype.c#L438-L455) validates only MP_UNARY_OP_HASH and MP_UNARY_OP_INT_MAYBE; MP_UNARY_OP_LEN hits the default: arm and returns the raw __len__ value unchecked. CPython's slot_sq_length runs the result through _PyNumber_Index — TypeError for non-index types — then rejects negatives with ValueError.
Fix Suggestion
Add an MP_UNARY_OP_LEN case to the switch that coerces via mp_obj_get_int (raises TypeError for None/str/float) and raises ValueError on a negative result.
Code of Conduct
Yes, I agree
Port, board and/or hardware
Unix port, Linux
MicroPython version
MicroPython v1.28.0-624-gb9cceaa6fd on 2026-07-24
Reproduction
Expected behaviour
CPython raises (3.11.15):
Observed behaviour
MicroPython returns the value unchanged:
A
strorfloatreturn is passed through the same way, and a negativeintis returned as-is instead of raisingValueError: __len__() should return >= 0.Additional Information
A negative
__len__flows unchecked into buffer-size arithmetic.bytearray(obj)andarray.array(t, obj)carry the value throughinstance_unary_opintoarray_construct, which stores it as asize_t— a negative small int wraps to a huge value — and hands it toarray_new:micropython/py/objarray.c
Lines 150 to 157 in b9cceaa
micropython/py/objarray.c
Line 110 in b9cceaa
Concretely,
bytearray(obj)with a__len__returning-1attempts to allocateSIZE_MAXbytes (MemoryError: ... allocating 18446744073709551615 bytes). Validating the result ininstance_unary_oprejects it there. Related issues on the same unchecked-__len__surface: #18617 (bytearray buffer overflow via a length/iterator mismatch) and #18620 (len * itemsizeoverflow).Root Cause
instance_unary_op(py/objtype.c#L438-L455) validates onlyMP_UNARY_OP_HASHandMP_UNARY_OP_INT_MAYBE;MP_UNARY_OP_LENhits thedefault:arm and returns the raw__len__value unchecked. CPython'sslot_sq_lengthruns the result through_PyNumber_Index—TypeErrorfor non-index types — then rejects negatives withValueError.Fix Suggestion
Add an
MP_UNARY_OP_LENcase to theswitchthat coerces viamp_obj_get_int(raisesTypeErrorforNone/str/float) and raisesValueErroron a negative result.Code of Conduct
Yes, I agree