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

[Bug] Calling inherited sensor methods crashes MicroPython #777

Closed
laurensvalk opened this issue Nov 4, 2022 · 13 comments
Closed

[Bug] Calling inherited sensor methods crashes MicroPython #777

laurensvalk opened this issue Nov 4, 2022 · 13 comments
Assignees
Labels
bug Something isn't working software: pybricks-micropython Issues with Pybricks MicroPython firmware (or EV3 runtime) topic: MicroPython Issues related to the MicroPython programming language

Comments

@laurensvalk
Copy link
Member

Describe the bug
Classes that inherit a sensor will crash when calling inherited methods.

It's possible we don't have the right flags enabled to support this.

Now that we support multi-file projects, making classes is kind of expected/encouraged, so we should probably re-enable them if we can.

To reproduce

from pybricks.parameters import Port
from pybricks.iodevices import PUPDevice

class NewSensor(PUPDevice):

    def __init__(self, port):

        # Read-only so doesn't crash but gives bad value:
        print(self.info())

        # Crashes:
        self.read(0)

sensor = NewSensor(Port.E)
@laurensvalk laurensvalk added bug Something isn't working software: pybricks-micropython Issues with Pybricks MicroPython firmware (or EV3 runtime) topic: MicroPython Issues related to the MicroPython programming language labels Nov 4, 2022
@dlech
Copy link
Member

dlech commented Nov 4, 2022

I don't think there are any config switches for this. I think we just have to call super().__init__() as the first line of the constructor if we want to access the base class inside of the __init__() method.

If we really want to avoid this crash, we could possibly define a __new__ attribute to make mp_obj_instance_make_new() take a different code path. (This seems like an expensive fix in terms of code size to me compared to how often this is likely to happen.)

https://github.com/pybricks/micropython/blob/d21eef4993dac9d62b7cbaa30b9777bb36314484/py/objtype.c#L277

Or maybe we need to add checks that fields are initialized (not zero) before using them?

@laurensvalk
Copy link
Member Author

Yep, planning to look at this soonish, there’s some things I was thinking to rework in this area anyway.

@laurensvalk laurensvalk self-assigned this Nov 6, 2022
@laurensvalk
Copy link
Member Author

laurensvalk commented Nov 6, 2022

On closer inspection, it isn't due to inheritance per se. It also crashes with simply PUPDevice.read(PUPDevice, 0). Simpler types (StopWatch) also misbehave to a lesser extent.

It's perhaps just because we don't assert the type of self in each of our methods.

We could do that, but we'd need to consider the trade off with build size.

On that note, I've been thinking we could do some fun tricks to reduce the code size of sensor types...

I don't think there are any config switches for this. I think we just have to call super().init() as the first line of the constructor if we want to access the base class inside of the init() method.

They don't have a __init__ method, so overriding __init__ isn't really supported. But as mentioned above, that may not have been the root cause.

We could probably encourage composition over inheritance instead.

@dlech
Copy link
Member

dlech commented Nov 6, 2022

We could probably encourage composition over inheritance instead.

👍

@dlech
Copy link
Member

dlech commented Nov 11, 2022

This works:

from pybricks.parameters import Port
from pybricks.iodevices import PUPDevice

class NewSensor(PUPDevice):

    def __init__(self, port):
        super().__init__(port)
        print(self.info())
        print(self.read(0))

sensor = NewSensor(Port.C)

@dlech
Copy link
Member

dlech commented Nov 11, 2022

On closer inspection, it isn't due to inheritance per se. It also crashes with simply PUPDevice.read(PUPDevice, 0). Simpler types (StopWatch) also misbehave to a lesser extent.

We should probably open a separate issue for this one, but there is MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG to control the behavior of mp_check_self(), so we could add this everywhere and disable it on Move hub to save space.

@laurensvalk
Copy link
Member Author

there is MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG to control the behavior of mp_check_self(),

It costs 156 bytes on Move Hub. Considering that it prevents a crash, that seems like it would be worth it.

@laurensvalk
Copy link
Member Author

I see your WIP commmit now. That's nice for a more descriptive error, but we shouldn't need it to just fix the crash. Just enabling MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG suffices to raise a TypeError.

@dlech
Copy link
Member

dlech commented Nov 16, 2022

Considering that it prevents a crash, that seems like it would be worth it.

Except it doesn't fix the crash in the __init__() method in the original comment here.

It only fixes crashing in cases like to list.append(str, 0) and only for MicroPython builtins, not in any Pybricks code. The WIP branch extends it to fix Pybricks code like PUPDevice.read(PUPDevice, 0).

Calling methods in __init__() without calling super().__init__() first still crashes with the WIP branch (which is why I suggested we consider PUPDevice.read(PUPDevice, 0) to be a separate issue).

@laurensvalk
Copy link
Member Author

The WIP branch extends it to fix Pybricks code like PUPDevice.read(PUPDevice, 0).

Just enabling the check self config (without the WIP commit) fixed this for me as well.

@laurensvalk
Copy link
Member Author

Except it doesn't fix the crash in the init() method in the original comment here.

I split it to separate issue.

If we really want to avoid this crash, we could possibly define a new attribute to make mp_obj_instance_make_new() take a different code path. (This seems like an expensive fix in terms of code size to me compared to how often this is likely to happen.)

Agreed that this might be too expensive. Alternatively, it could be handled by the .attr function but that may also be too expensive.

I was hoping we could avoid writing any user code that could crash the hub, but maybe this is just one of those limitations we just have to document.

@laurensvalk
Copy link
Member Author

After a closer look, it does look like this is pretty much the same as #805. Better yet, it can be fixed. It could be done with a variant of the WIP branch (and defining the check self macro correctly, and also doing it manually for non-kwarg funcs), but I'll have a look whether we can do it with a smaller code size impact.

@laurensvalk
Copy link
Member Author

Found it. One line of code :)

laurensvalk added a commit to pybricks/pybricks-micropython that referenced this issue Nov 17, 2022
laurensvalk added a commit to pybricks/pybricks-micropython that referenced this issue Nov 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working software: pybricks-micropython Issues with Pybricks MicroPython firmware (or EV3 runtime) topic: MicroPython Issues related to the MicroPython programming language
Projects
None yet
Development

No branches or pull requests

2 participants