Object-centric breakpoints: a tutorial

The new Pharo debugger is shipped with object-centric breakpoints. An object-centric breakpoint is a breakpoint that applies to one specific object, instead of being active for all instances of that object’s class. We have two kinds of object-centric breakpoints:

  • The haltOnCall breakpoint: give it a method selector and a target object, and the system will halt whenever the target object executes the corresponding method.
  • The haltOnAccess breakpoint: for a target object, the system will stop whenever the state of that target object is read or written. It is possible to specify which variable whose accesses will trigger the breakpoint.

Installation

If you use the object-centric experimental image, you can skip this part and start the tutorial. Else, follow installation instructions here https://github.com/StevenCostiou/Pharo-Object-Centric-Tutorial.

The tutorial

Context: the OCDBox objects loop

In this example, we will use a class named OCDBox. It models a trivial object named box that holds objects. It has two instance variables, elements and name, and a few methods in its API. In particular, the method addElement: adds an object into the box.

We will use a test to practice object-centric breakpoints. In this test, we instantiate 100 boxes. We iterate over all these boxes to:

  • add an object to each box,
  • print the box on the Transcript.

When you execute the test, the Transcript shows you each box that is printed in the iteration loop. If you look at the code of the OCDBox class, you will see that:

  • adding an element to a box modifies its name,
  • the printing method uses the name to display the box in the Transcript.

Object-centric breakpoints

In the following, we demonstrate the use-cases of the haltOnCall and haltOnAccess breakpoints. Each time, we start by demonstrating the breakpoint through a video, then we explain the use-case and how to identify situations where that object-centric breakpoint might help. All videos have sound.

Warming up with object-centric breakpoints

The following video shows how to install object-centric breakpoints through the inspector. Basically, we create two box objects b1 and b2, and we install various object-centric breakpoints on b2.

Installing object-centric breakpoints
  • We start by inspecting b2,
  • We select the name instance variable in the inspector, and install an object-centric breakpoint through the contextual menu:
    • a halt on read will stop the execution each time the name variable is accessed,
    • a halt on write will stop the execution each time the name variable is written to,
    • a halt on access will stop on both read and writes of name,
  • We select one of the methods of b2 in the inspector, and install a halt on call through the contextual menu:
    • each time this method is called, and the receiver is b2, then the execution halts

In our example, we install a halt on read breakpoint on the name instance variable of b2, and a halt on call breakpoint on the name: method of b2. For each case, the execution will halt only for methods reading the name instance variable in b2, or when b2 calls its name: method. The execution will not halt for b1, nor for any other object.

In the following, we apply our breakpoints on a debugging example. Instead of using the inspector, we install object-centric breakpoints directly from the debugger, as we would do in a real debugging session.

Removing object-centric breakpoints

Object-centric breakpoints are garbage collected along with objects: if your object disappears, then so does the breakpoint.

For existing objects, removing an object-centric breakpoint is as simple as inspecting the object (for example in the debugger), going into the breakpoint pane, then selecting the breakpoint and using the context menu to remove it.

Stopping when a specific object receives a particular message

The test iterates over a hundred boxes objects, and to each object it sends the addElement: message. In this exercise, we select one box object among all the boxes the test is iterating, and we install an object-centric breakpoint on the addElement: method of that object. Then, we proceed the test and the addElement: method is called on each of the boxes. The execution only stops when the selected box executes the addElement: method.

Getting to the object of interest

The first step to use an object-centric breakpoint is to get to the object you want to debug.

For now, we do that by putting a first breakpoint into our code. When the execution stops, we navigate in the debugger to find objects of interest and install object-centric breakpoints.

In the following, we put a self halt. in the test, before iterating over the boxes objects. We execute the test and start from there.

Breaking when the box of interest recives the #addElement: message
  1. After adding a halt (see above), we execute the test:
    • The execution halts and opens a debugger,
    • we are able to select an object to debug it.
  2. We select one of the boxes in the boxes collection:
    • Double-click on the boxes variable in the bottom inspector,
    • choose a box in the items pane by double-clicking on the box.
  3. In the opening inspector, go into the meta-side and select the addElement: method
  4. Right-click on that method, and select halt on call in the menu:
    • The breakpoint is installed on this method, and scoped to our box object,
    • you can see the breakpoint in the breakpoint pane (if you look at other boxes, there are no breakpoint).
  5. Proceed the execution:
    • The test iterates over the boxes and sends the addElement: message to each box,
    • only the box that you instrumented breaks on this method.
Use-cases for the halt on call breakpoint

The typical use-case for this object-centric breakpoint is when you have many instances of the same class running in your program, while you are interested to debug a method for one specific object among these instances. You are interested to answer the following question: “When is this particular object executing this particular method during the execution?”

Our box example illustrates this case. You need to debug a target method for a specific instance of OCDBox, while the test iterates over a hundred of them. Putting a breakpoint in the OCDBox class will stop the execution for every instance calling the target method.

Now imagine that you want to debug a display method in a graphical object. You will have much more instances sharing that display method. Using conventional breakpoints is tedious because it will stop every time one of the graphical object uses that display method.

In contrast, you use the halt on call breakpoint to debug a specific method for a specific object because:

  • You want to avoid designing super-complex conditional breakpoints to filter your object — sometimes you don’t even have enough information to discriminate your object,
  • you do not know when (or if!) the object you want to debug will call the method, so you may not know where to insert a standard breakpoint in the code — and there might be a lot of call sites to that method so you do not know which one your object will go through,
  • you want to avoid stepping many times in the debugger before getting to the point where your object calls the method to debug — also you do not want to miss that point by extra-stepping by mistake!

Breaking when the state of a specific object is written to

In this exercise, we reuse our test iterating over a hundred box objects. We select again a box among all the iterated boxes, but this time we want to stop when the name instance variable of that box is written to. To that end, we install an object-centric breakpoint on all write accesses to that variable in the selected object. Then, we proceed the test and the execution only breaks when the name instance variable of the selected box is modified.

Breaking when the name instance variable of our box object is written

As before, we execute the test and a debugger opens before the boxes are iterated in a loop. In this loop, each box is being printed one by one through the crTrace method.

We select one of the boxes in the boxes collection:

  • Double-click on the boxes variable in the bottom inspector,
  • choose a box in the items pane by double-clicking on the box.

In the opening inspector, go into the raw view and select the name instance variable. Right-click on that method, and select halt on write in the menu:

  • The breakpoint is installed on all write accesses to this variable, and scoped to our box object,
  • you can see the write accesses and the breakpoint installed on them in the breakpoint pane.

We proceed the execution and it breaks when the name variable of the selected box is modified. There is no halt when other boxes have their name variable modified.

Use-cases for the halt on access breakpoint

The typical use-case for this object-centric breakpoint is when you have many instances of the same class running in your program, while you are interested to know when the state of one specific object is modified. You are interested to answer the following question: “When is the state of this particular object modified during the execution?”

Our box example illustrates this case. Putting a breakpoint on every access to the name variable is long, tedious and error-prone (you may forget some). Watchpoints, Variable Breakpoints or Data breakpoints may help: those tools are able to stop execution when you access an instance variable defined in a class. However, they will stop the execution each time the name variable is modified in any instance of the OCDBox class (here, at each loop iteration!).

Imagine again that you are debugging a graphical object. You are interested to know when a given property of that object (i.e., an instance variable) is modified. You do not want all the graphical objects sharing a property to break then execution when that property is modified.

Instead, you use the halt on access breakpoint to stop the execution of a program when a particular property of a specific object is modified:

  • You want to avoid designing super-complex conditional breakpoints to filter your object — sometimes you don’t even have enough information to discriminate your object,
  • you do not know when (or if!) the property will be modified, so you may not know where to insert a standard breakpoint in the code — there might be a lot of methods modifying that property, and many call sites to those methods so you do not know which one your object will go through,
  • you want to avoid stepping many times in the debugger before getting to the point where your object calls the method to debug — and you cannot guarantee that the point where you stopped in the execution will actually modify the variable!

Conclusion: Using object-centric breakpoints for real

We have seen and practiced two kinds of object-centric breakpoints, the halt on call and the halt on access breakpoints. We used a simple example to illustrate how to use those breakpoints. However, in reality it is much more complex to decide when and how to use such breakpoints. Let’s review a few advices:

  • First, investigate to know what you have in hands,
  • put a first breakpoint at a strategic place to find your object, or inspect directly the object if you have it,
  • then discriminate: what is the information you need to find?
    • do you need to know why an object seems to not execute a method properly? Use a halt on call breakpoint on this method!
    • do you need to know when or how an instance variable is modified in an object? Use a halt on write breakpoint on this variable!

Object-centric breakpoints are not a magickal tool and, often, you may not find directly your problem. The execution may stop a few (or many!) times in the same place before you understand your problem. Still object-centric breakpoints are a faster, easier way than conventional breakpoints to find the information you need from your execution.

There are also risks: similarly to conventional breakpoints, an object-centric breakpoint will halt the execution as many times as it is hit at run time. If you install any breakpoint in code that is executed very often, you may interrupt your program for good. Think that if you put a break point on the code editor keystroke behavior, you may not be able to write code anymore!

As a closing note, other breakpoints do exist: breakpoints that stop the execution each time a particular object receives any message, or when two specific objects interact together. These breakpoints are not implemented yet, but they are scheduled for the near future!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: