TRACE doesn't bind I/O control variables effectively
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
SBCL |
New
|
Undecided
|
Unassigned |
Bug Description
Ran into some troubles using TRACE to debug a routine called in a program that uses some peculiar pretty printing routines. In poking around at it, found the following:
1. TRACE-END-
2. TRACE-END-
3. TRACE-START-
Items 1 and 2 are defects, in that the dynamic environment on the way out of a traced call might be hostile for printing. Reproduction code for both below.
Item 3 is mostly an aesthetic concern around symmetry, though is arguably a functional shortcoming.
Attached is a patch that does the following:
A. Add a new exported variable, SB-DEBUG:
B. Add an unexported macro, WITH-TRACE-
C. Have TRACE-START-
D. Finally, move the BREAK calls outside of the controlled I/O environment, so that BREAK gets called in traced function's dynamic environment, not TRACE's dynamic environment. (SBCL's default debugger will rebind everything anyway, but I think the distinction is observable if someone has a custom *DEBUGGER-HOOK*.)
Bug report requirements:
SBCL version: 2.0.6.115-
uname -a: Darwin <redacted> 19.6.0 Darwin Kernel Version 19.6.0: Sun Jul 5 00:43:10 PDT 2020; root:xnu-
*FEATURES*: (:X86-64 :GENCGC :64-BIT :ANSI-CL :BSD :COMMON-LISP :DARWIN :IEEE-FLOATING-
Reproduction file follows.
(in-package "CL-USER")
;;; Problem 1: TRACE doesn't control the I/O environment when printing
;;; the results of the traced call.
;; Suppose you've got a pretty printer routine that sometimes errors.
;; (One way to sometimes error is to always error.)
(defun pprint-
(declare (ignore stream integer))
(error "Oops!"))
(defvar *my-pprint-
(set-pprint-
0 *my-pprint-
;; Suppose you've got a routine that sometimes returns a value that
;; your pretty printer sometimes chokes on.
(defun foo () 5)
;; Trace that function.
(trace foo)
;; This will TRACE just fine.
(foo)
;; But this will fail in TRACE itself.
(let ((*print-
(
(foo))
;;; Problem 2: TRACE-AFTER routine runs in the user's I/O environment.
;;; This requires a slightly more contrived reproduction.
;; Suppose you're using WITH-STANDARD-
;; cheerfulC-
;; READing later, but you want to be vaguely principled about it, so
;; you use WITH-STANDARD-
;; you give special treatment to anything not standardly readable.
(defvar *pprint-
(defmacro with-my-
`(with-
(let ((*read-eval* nil)
(*print-
,@body)))
;; Hash tables have no standard read syntax, so you make up a syntax
;; for it.
(defun pprint-hash-table (stream hash-table)
(let ((test-name (find (hash-table-test hash-table) '(eq eql equal equalp)
:test #'(lambda (x y)
(eql (coerce x 'function)
(coerce y 'function)))))
stuff)
(maphash #'(lambda (key value) (push (cons key value) stuff)) hash-table)
(write-string "#H" stream)
(write (cons test-name stuff) :stream stream)
nil))
(set-pprint-
0 *pprint-
;; Now let's have a function that returns a hash table.
(defun bar ()
(make-
;; Trace it without :PRINT-AFTER
(trace bar)
;; This will print the hash table using my pretty-printer routine
(with-my-
(bar))
;; But trace it again with a PRINT-AFTER...
(trace bar :print-after (sb-debug:arg 0))
;; ...and this will break.
(with-my-
(bar))
if someone gets into patching trace, the debugger, and printing, then i also have this lying around for a long while:
https:/ /github. com/attila- lendvai/ sbcl/commit/ c497e9fa7343a3e 649b30738fdfbca 46e8826a43
(it's not directly relevant for this issue, but certainly related)