How does Pharo starts debugger when message is not understanded?
Asked Answered
P

2

5

When you start a code sending not implemented message, Pharo launches debugger.

As far as I understand it works through Object >> doesNotUnderstand, which trigger exception, and this leads to debugger window.

enter image description here

The question is what exactly does Object >> doesNotUnderstand do, and how is that different to other interactive helpers, like one starting on not existent variable?

Preface answered 2/2, 2019 at 19:42 Comment(1)
Nearly the same question, but for Squeak: #51363800 Just for the record. The answer is slightly different because of implementation details varying between Squeak and Pharo, but conceptually the control flow is the same.Twosome
K
7

The debugger is opened as a response to an unhandled exception. To better explain we can start from triggering an exception that is not caught anywhere in the system. For example, we can execute in a Playground Error signal: 'an error'. (signal is throwing an error in Pharo). This opens the following debugger:

enter image description here

When an exception happens the system first tries to find an exception handler for that exception. If no exception handler is found then the system sends the message defaultAction to the exception. This is implemented in the class Error as:

Error>>#defaultAction
    "No one has handled this error, but now give them a chance to decide how 
    to debug it.  If none handle this either then open debugger 
    (see UnhandedError-defaultAction)"

    UnhandledError signalForException: self

So the system responds to an unhandled exception by throwing another exception, UnhandledError. Again if no exception handlers are found for the new exception the system sends the message defaultAction to the exception object. In this case the exception is an instance of UnhandledError and this class overriders defaultAction with the following implementation:

UnhandledError>>#defaultAction
    <reflective: #unhandledErrorDefaultAction:message:>
    ^ UIManager default unhandledErrorDefaultAction: self exception

The method unhandledErrorDefaultAction: is quite simple and just sends the exception object the message debug.

MorphicUIManager>>#unhandledErrorDefaultAction: anException
    anException debug

The method debug is implemented in Exception, the root class of all exceptions in Pharo as:

Exception>>#debug
    "open a debugger on myself"
    Processor activeProcess 
        debug: self signalerContext
        title: self description

So this is what opens the debugger.

In the case of sending an unknown message to an object an exception of type MessageNotUnderstood is thrown by the method Object>>#doesNotUnderstand:. This exception follows the same chain as before and the system ends up sending the debug message to the MessageNotUnderstood exception which opens the debugger.

So in short:

  • when an unknown message is sent to an object the system sends the object the message #doesNotUnderstand:;
  • doesNotUnderstand: raises an exception of type MessageNotUnderstood;
  • if this exception is not caught another exception of type UnhandledError is raised;
  • if this new UnhandledError is not caught it asks the UI manager to handle this case;
  • the UI manager sends the message debug to the initial exception, in this case the MessageNotUnderstood exception (only MorphicUIManager does this; other UI managers like CommandLineUIManager do other actions like existing the image);
  • the debug message opens the debugger

The handler for non existent variables has a totally different implementation that is in the actual compiler. When code is compiled in a class that has unknown variables the compiler detects that and asks the user what to do: create a new instance variable or a local parameter.

If you try to execute code containing an unknown class, like UnnknowsClass new. the compiler also detects this and asks the user what to do. There is no exception raised in this case.

There are other helpers that use the same mechanism as doesNotUnderstand: for example raising notifications. If you execute Object new notify: 'a notification' the method notify: throws a Warning exception that ends up opening the debugger.

enter image description here

Karlenekarlens answered 2/2, 2019 at 20:36 Comment(0)
F
5

Just to complement the excellent answer given by Adrei Chis, let me add that the message #doesNotUnderstad: is a little bit different from other messages.

Every time a message is sent, the Runtime (usually in the Virtual Machine), finds the method that corresponds to the intended receiver for the selector of the message being sent.

It does this by looking at the receiver's behavior. If no method is found it goes to the inherited behavior (usually the one defined in the superclass), and continues this way until it finds the method or the inheritance chain is exhausted. This search is called method lookup.

In the second case (when no method exists in the behavior hierarchy for the object), the Runtime (1) reifies the message by creating a Message object with the selector not found and the actual arguments (if any) and (2) sends the receiver a new message with selector #doesNotUnderstand: and argument the message just reified.

The lookup process repeats, and (most likely) this time the selector #doesNotUnderstand: is found for the receiver (which could have implemented its own version, or have inherited it from the top of the class hierarchy). At this point the steps described by Adrei follow.

If for any reason the receiver does not understand #doesNotUnderstand: (pun intended), the Runtime cannot continue and closes the system (what else it could do?)

Note also that the lookup is a little bit different if the message was sent to super. But that is another story and the basic ideas w.r.t. this question remain.

Fearless answered 3/2, 2019 at 2:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.