Delving into the Factor debugger

As I mentioned in Tuesday's post, I'm writing about what I learned from backwards-debugging in Factor, that is, looking at the debugging information available after figuring out the bug independently.

The bug I had inserted was this:

In running unit tests for protagonist, I had wanted to do a "setUp" and "tearDown", because the test involves creating a tagsystem and then trying different things with it. So, I didn't want to create the tagsystem over and over again. I'm not sure if this is idiomatic Factor or not, but what I did was to use the SYMBOL word.

SYMBOL is considered a parsing word, because it is used to define new words. A word defined with SYMBOL is a like a boxed mutable variable that can be set with set and read with get. So, if you push a symbol on the stack, it is not the same as pushing its value on the stack. My bug was that I tried to use a symbol as the argument to a function. I forgot to get the value out first.

My bug involved the word delete-tree, which recursively deletes a directory. Since deleting directory trees is a bit dangerous, I replicated the behaviour with make-directory.

IN: scratchpad SYMBOL: s
IN: scratchpad "s" s set
IN: scratchpad USE: io.directories
IN: scratchpad s make-directory
Generic word absolute-path does not define a method for the word class.
Dispatching on object: s

Type :help for debugging help.
IN: scratchpad

So we have a call to absolute-path, which is a generic word. It is generic, because it is defined polymorphically for both pathnames and strings. The error is telling me that absolute-path has been called on a word. However, I didn't call absolute-path. This initially confused me in my situation, because I did call absolute-path elsewhere in the actual code under test, and so I was misdirected for a time.

So let's try that :help suggestion.

IN: scratchpad :help
no-method ( object generic -- * )


Inputs and outputs
an object
generic a generic word

Word description
Throws a no-method error.

Error description
Thrown by the generic word to indicate it does not have a method for the class of object.

IN: generic.single
TUPLE: no-method { object read-only } { generic read-only } ;

USING: kernel ;
IN: generic.single
: no-method ( object generic -- * ) \ no-method boa throw ;

USING: accessors classes debugger generic.single io kernel
prettyprint ;
M: no-method error.
"Generic word " write dup generic>> pprint
" does not define a method for the " write
object>> class-of pprint " class." print
"Dispatching on object: " write object>> short. ;

USING: generic.single kernel summary ;
M: no-method summary drop "No suitable method" ;

Those are nice docs, but it doesn't get me closer to solving the problem. However, at the bottom of all that, there are more hints:

Debugger commands:

:s - data stack at error time
:r - retain stack at error time
:c - call stack at error time
:edit - jump to source location (parse errors only)
:get ( var -- value ) accesses variables at time of the error
:vars - list all variables at error time

In Factor there are 5 stacks that make up a continuation

  • The data stack is the stack. It is where arguments are drawn from and returned to, and this is what makes the language stack-based.
  • The retain stack is a kind of swap space, and I don't know exactly when and why it is used.
  • The call stack keeps track of words in progress, which means it will contain a traceback.
  • The name stack holds variable bindings.
  • The catch stack probably has something to do with exception handling.

Let's try them all! (Well, I'll skip :edit and :get.)

IN: scratchpad :s
T{ no-method f s absolute-path }

Okay, so s was still on the stack, and then this thing T{ no-method f s absolute-path }, which is a tuple class object no-method, as described in detail above.

IN: scratchpad :r
{ }

It looks like it has an empty sequence in it. More on this later.

IN: scratchpad :c
(U) Quotation: [ c-to-factor -> ]
Word: c-to-factor
(U) Quotation: [ [ catchstack* push ] dip call -> catchstack* pop* ]
(O) Word: command-line-startup
(O) Word: listener
(O) Word: (listener)
(U) Quotation: [
[ ~quotation~ dip swap ~quotation~ dip ] dip swap
[ call datastack ] dip -> swap [ set-datastack ] dip
(U) Quotation: [ call -> datastack ]
(O) Method: M\ unix make-directory
(O) Method: M\ object absolute-path
(O) Word: no-method
(O) Method: M\ object throw
(U) Quotation: [
OBJ-CURRENT-THREAD special-object error-thread set-global
current-continuation -> error-continuation set-global
[ original-error set-global ] [ rethrow ] bi

So now I can see that the absolute-path method was called by make-directory, which I was directly responsible for.

IN: scratchpad :vars
hashtable with 2 entries
inotify T{ input-port f t f ~fd~ f ~buffer~ }
watches H{ { 1 ~linux-monitor~ } { 2 ~linux-monitor~ } { 3...

And those are some variables that I don't know anything about.

Well, the answer is in there. When I called make-directory, it called absolute-path, and this was called on a word that doesn't have that method defined. The hint about which word was having the problem is given in the data stack report.

So, now I have a better idea of how to use what is given to find the point in my code where the problem is. Next time maybe I'll be able to resolve it more quickly.

Plan and Actual, with a few comments


  • Haskell:

    • Do problems 5 and 6 from homework one of Yorgey's Haskell course.

      I notice that I am intimidated at this point, because number 6 is marked as "optional", which I take to mean "hard". Somewhere along the line I have started doubting my ability to do hard problems, which is ridiculous. Time to get over it. I guess the worst that can happen is that I'll get really stuck, and I'll have to (get to?) go ask someone around here for help.

  • Tahoe-LAFS:

    • Complete the first draft of my design proposal and ask Zooko to proofread it before I put it on the dev list.

      (This is verbatim from yesterday's list. Didn't get to it.)

  • Factor / Protagonist:

    • Write up what I learned from yesterday's debugging.
    • I'm tempted to now try the exercises from Yorgey in Factor.
  • Light reading:


  • Haskell:
    • Completed problem 5, quickly, and spent a long time trying to come up with an implementation of 6 that was as fast as the claim. I didn't solve it, though I did get some progressively faster solutions. Decided to let it rest. The trickiness is in figuring out a clever algorithm, not in using Haskell correctly. So, while I could look up what Knuth did, and I'm sure it would be interesting, I think I'll move forward with the Haskell lessons.
  • Reading:
  • Tahoe-LAFS:
    • Thought more deeply through the design I had envisioned for solving symlinks, and decided I agree with the devs after all, that the only right way to do it involves supporting the notion of symlinks in LAFS. Through that process, I did come up with a nice design for integration of protagonist with LAFS, however.