7.4: The Debugger
- Page ID
- 39608
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)The debugger is arguably the most powerful tool in the Pharo tool suite. It is used not just for debugging, but also for writing new code. To demonstrate the debugger, let’s start by creating a bug!
Using the browser, add the following method to the class String
:
suffix "assumes that I'm a file name, and answers my suffix, the part after the last dot" | dot dotPosition | dot := '.'. dotPosition := (self size to: 1 by: -1) detect: [ :i | (self at: i) = dot ]. ^ self copyFrom: dotPosition to: self size
Of course, we are sure that such a trivial method will work, so instead of writing an SUnit test, we just type 'readme.txt' suffix
in a playground and Print it
(p
). What a surprise! Instead of getting the expected answer 'txt'
, a PreDebugWindow
pops up, as shown in Figure \(\PageIndex{1}\).

The PreDebugWindow
has a title bar that tells us what error occurred, and shows us a stack trace of the messages that led up to the error. Starting from the bottom of the trace, UndefinedObject>>DoIt
represents the code that was compiled and run when we selected 'readme.txt' suffix
in the playground and asked Pharo to Print it
. This code, of course, sent the message suffix
to a ByteString object ('readme.txt')
. This caused the inherited suffix method in class String
to execute; all this information is encoded in the next line of the stack trace, ByteString(String)>>suffix
. Working up the stack, we can see that suffix sent detect:...
and eventually detect:ifNone sent errorNotFound:
.
To find out why the dot was not found, we need the debugger itself, so click on Debug
. You can also open the debugger by clicking on any of the lines on the stack trace. If you do this, the debugger will open already focused on the corresponding method.
The debugger is shown in Figure \(\PageIndex{2}\); it looks intimidating at first, but it is quite easy to use. The title bar and the top pane are very similar to those that we saw in the PreDebugWindow
. However, the debugger combines the stack trace with a method browser, so when you select a line in the stack trace, the corresponding method is shown in the pane below. It’s important to realize that the execution that caused the error is still in your image, but in a suspended state. Each line of the stack trace represents a frame on the execution stack that contains all of the information necessary to continue the execution. This includes all of the objects involved in the computation, with their instance variables, and all of the temporary variables of the executing methods.

In Figure \(\PageIndex{2}\) we have selected the detect:ifFound:IfNone:
method in the top pane. The method body is displayed in the center pane; the blue highlight around the message value
shows that the current method has sent the message value
and is waiting for an answer.
The Variables
pane at the bottom of the debugger is actually like a inspector (without playground pane). You can select one variable and open another inspector pane with the well-known inspector panes (Raw, ...), and an Evaluator
acting as a Playground for evaluating code within the context of the selected variable. The variables list has up to four types variables it shows.
- parameter any parameter passed to this method.
- temp any temporaries used in this method.
- attribute any instance variable accessible from the context of the current receiver.
- implicit pseudo variables (
self
,thisContext
,stackTop
) used in the current context.
As you select different stack frames, the identity of self
may change, and so will the contents of the Variables
list. If you click on self
in the bottom-left pane, you will see that self
is the interval (10 to: 1 by -1
), which is what we expect. You can always select self
and select the Evaluator
pane to evaluate some code in the content of the current receiver. But because all of the variables are also in scope in the method pane; you should feel free to type or select expressions directly in the method pane and evaluate them. You can always Cancel
(l
) your changes using the menu or CMD-l
.
Selecting thisContext
from the list of (implicit) variables, shows the current context object.
As we can see one method lower in the stack trace, the exceptionBlock
is [self errorNotFound: ...]
, so, it is not surprising that we see the corresponding error message.
Incidentally, if you want to open a full inspector on one of the variables shown in the mini-inspectors, just double-click on the name of the variable, or select the name of the variable and right-click to choose Inspect (i)
. This can be useful if you want to watch how a variable changes while you execute other code.
Looking back at the method window, we see that we expected the penultimate line of the method to find '.'
in the string 'readme.txt'
, and that execution should never have reached the final line. Pharo does not let us run an execution backwards, but it does let us start a method again, which works very well in code that does not mutate objects, but instead creates new ones.
Click Restart
, and you will see that the focus of execution returns to the first statement of the current method. The blue highlight shows that the next message to be sent will be do:
(see Figure \(\PageIndex{3}\)).

The Into
and Over
buttons give us two different ways to step through the execution. If you click Over
, Pharo executes the current message-send (in this case the do:
) in one step, unless there is an error. So Over
will take us to the next message-send in the current method, which is value
– this is exactly where we started, and not much help. What we need to do is to find out why the do:
is not finding the character that we are looking for.
After clicking Over
, click Restart
to get back to the situation shown in Figure \(\PageIndex{3}\).
Click Into
two times; Pharo will go into the method corresponding to the highlighted message-send, in this case, Interval>>do:
.
However, it turns out that this is not much help either; we can be fairly confident that Interval>>do:
is not broken. The bug is much more likely to be in what we asked Pharo to do. Through
is the appropriate button to use in this case: we want to ignore the details of the do:
itself and focus on the execution of the argument block.
Select the detect:ifFound:IfNone:
method again and Restart
to get back to the state shown in Figure \(\PageIndex{3}\). Now click on Through
a few times. Select each
in the context window as you do so. You should see each
count down from 10 as the do:
method executes.
When each
is 7 we expect the ifTrue:
block to be executed, but it isn’t. To see what is going wrong, go Into
the execution of value:
as illustrated in Figure \(\PageIndex{4}\).

After clicking Into
, we find ourselves in the position shown in Figure \(\PageIndex{5}\). It looks at first that we have gone back to the suffix
method, but this is because we are now executing the block that suffix
provided as argument to detect:
.

If you select dot
in the context inspector, you will see that its value is '.'
. And now you see why they are not equal: the seventh character of 'readme.txt'
is of course a Character
, while dot
is a String
.
Now that we see the bug, the fix is obvious: we have to convert dot
to a character before starting to search for it.
Change the code right in the debugger so that the assignment reads dot := $
. and accept the change.
Because we are executing code inside a block that is inside a detect:
, several stack frames will have to be abandoned in order to make this change. Pharo asks us if this is what we want (see Figure \(\PageIndex{6}\)), and, assuming that we click yes, will save (and compile) the new method.

The evaluation of the expression 'readme.txt' suffix
will complete, and print the answer '.txt'
.
Is the answer correct? Unfortunately, we can’t say for sure. Should the suffix be .txt
or txt
? The method comment in suffix
is not very precise. The way to avoid this sort of problem is to write an SUnit test that defines the answer.
testSuffixFound self assert: 'readme.txt' suffix = 'txt'
The effort required to do that was little more than to run the same test in the playground, but using SUnit saves the test as executable documentation, and makes it easy for others to run. Moreover, if you add testSuffix
to the class StringTest
and run that test suite with SUnit, you can very quickly get back to debugging the error. SUnit opens the debugger on the failing assertion, but you need only go back down the stack one frame, Restart
the test and go Into
the suffix method, and you can correct the error, as we are doing in Figure \(\PageIndex{7}\). It is then only a second of work to click on the Run Failures
button in the SUnit Test Runner, and confirm that the test now passes.

Here is a better test:
testSuffixFound self assert: 'readme.txt' suffix = 'txt'. self assert: 'read.me.txt' suffix = 'txt'
Why is this test better? Because it tells the reader what the method should do if there is more than one dot in the target String.
There are a few other ways to get into the debugger in addition to catching errors and assertion failures. If you execute code that goes into an infinite loop, you can interrupt it and open a debugger on the computation by typing CMD-.
(that’s a full stop or a period, depending on where you learned English). (It is also useful to know that you can bring up an emergency debugger at any time by typing CMD-SHIFT-.
) You can also just edit the suspect code to insert Halt now..
So, for example, we might edit the suffix
method to read as follows:
suffix "assumes that I'm a file name, and answers my suffix, the part after the last dot" | dot dotPosition | dot := FileDirectory dot first. dotPosition := (self size to: 1 by: -1) detect: [ :i | (self at: i) = dot ]. Halt now. ^ self copyFrom: dotPosition to: self size
When we run this method, the execution of the Halt now
will bring up the pre-debugger, from where we can either proceed, or go into the debugger (and from there look at variables, step through the computation, and edit the code).
That’s all there is to the debugger, but it’s not all there is to the suffix
method. The initial bug should have made you realize that if there is no dot in the target string, the suffix
method will raise an error. This isn’t the behaviour that we want, so let’s add a second test to specify what should happen in this case.
testSuffixNotFound self assert: 'readme' suffix = ''
Lastly, add testNoSuffix
to the test suite in class StringTest
, and watch the test raise an error. Enter the debugger by selecting the erroneous test in SUnit, and edit the code so that the test passes. The easiest and clearest way to do this is to replace the detect:
message by detect:ifNone:
, where the second argument is a block that simply returns the string size.
We will learn more about SUnit in Chapter : SUnit.