Does this advice still apply? For example, are modern debugging aids still defeated in this way?
Common Lisp
In Common Lisp, tracing code is made by giving the name of the function we want to trace. This means that tracing can distinguish between different names, even though they refer to the same object. For example, in SBCL (and this says nothing about other implementations), we can do this.
Define foo
USER> (defun foo () 1)
FOO
Alias bar
to foo
:
USER> (setf (symbol-function 'bar) #'foo)
#<FUNCTION FOO>
Notice how the function itself has a name, foo
.
Calling bar
works:
USER> (bar)
1
Tracing bar
:
USER> (trace bar)
(BAR)
USER> (bar)
0: (PARROT.USER::BAR)
0: BAR returned 1
1
Notice how foo
is not traced:
USER> (foo)
1
I think it works because TRACE makes bar
become bound to a wrapper around its previous binding (i.e. #'foo
), so that calling bar
executes some tracing code around foo
, but does not do the same for foo
itself.
Notice however that there is some strange behaviour when trying to trace foo
:
USER> (trace foo)
WARNING: FOO is already TRACE'd, untracing it first.
(FOO)
USER> (foo)
0: (PARROT.USER::FOO)
0: FOO returned 1
1
Curiously, bar
is not traced anymore (well it said that it untrace'd it first, which probably untraced bar
):
USER> (bar)
1
Moreover, trace
can encapsulate, or not, the traced function:
:ENCAPSULATE {:DEFAULT | T | NIL}
If T, the default, tracing is done via encapsulation (redefining the
function name) rather than by modifying the function. :DEFAULT is
not the default, but means to use encapsulation for interpreted
functions and funcallable instances, breakpoints otherwise. When
encapsulation is used, forms are *not* evaluated in the function's
lexical environment, but SB-DEBUG:ARG can still be used.
When using encapsulate
with NIL, tracing bar
makes also foo
traced:
USER> (untrace)
T
USER> (bar)
1
USER> (foo)
1
USER> (trace bar :encapsulate nil)
(BAR)
USER> (bar)
0: (PARROT.USER::FOO)
0: BAR returned 1
1
USER> (foo)
0: (PARROT.USER::FOO)
0: BAR returned 1
1
And untracing foo
makes bar
untraced:
USER> (trace)
(BAR)
USER> (untrace foo)
T
USER> (bar)
1
Chicken Scheme
In Chicken Scheme for example tracing is an extension that relies on advice which in turns relies on an internal mechanism that mutates the procedure (there is a mention of a forward-table in the procedure), which means the procedure itself (not its name) is doing the tracing.
This looks like a lot like the :encapsulate nil
case above.
Conclusion
I would not rely on this aliasing to work because this looks a bit fragile, and not portable. Common Lisp allows you to define accessors in a way that look like functions, but expanded during compilation (either with inline
or with define-compiler-macro
), in case you are worried about performance. And in Scheme, you could maybe do the same, or load a different file that makes an alias instead of a wrapper when you deliver a program.
I think also that this is preferable to avoid optimizations unless you can identify actual bottlenecks when testing.