Skip to content

len() does not validate the value returned by __len__ #19512

Description

@jseop-lim

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:

None

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:

micropython/py/objarray.c

Lines 150 to 157 in b9cceaa

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_IndexTypeError 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugpy-coreRelates to py/ directory in source

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions