Remove superfluous ice-9-debugger-extensions code for old Guile versions
[bpt/guile.git] / module / ice-9 / gds-client.scm
CommitLineData
ea19f0b3
NJ
1(define-module (ice-9 gds-client)
2 #:use-module (oop goops)
3 #:use-module (oop goops describe)
ea19f0b3
NJ
4 #:use-module (ice-9 debugging trace)
5 #:use-module (ice-9 debugging traps)
6 #:use-module (ice-9 debugging trc)
7 #:use-module (ice-9 debugging steps)
8 #:use-module (ice-9 pretty-print)
9 #:use-module (ice-9 regex)
10 #:use-module (ice-9 session)
11 #:use-module (ice-9 string-fun)
12 #:export (gds-debug-trap
13 run-utility
ea19f0b3
NJ
14 gds-accept-input))
15
248ee86a 16(use-modules (ice-9 debugger utils))
ea19f0b3
NJ
17
18(use-modules (ice-9 debugger))
19
20(define gds-port #f)
21
22;; Return an integer that somehow identifies the current thread.
23(define (get-thread-id)
24 (let ((root (dynamic-root)))
25 (cond ((integer? root)
26 root)
27 ((pair? root)
28 (object-address root))
29 (else
30 (error "Unexpected dynamic root:" root)))))
31
32;; gds-debug-read is a high-priority read. The (debug-thread-id ID)
33;; form causes the frontend to dismiss any reads from threads whose id
34;; is not ID, until it receives the (thread-id ...) form with the same
35;; id as ID. Dismissing the reads of any other threads (by sending a
36;; form that is otherwise ignored) causes those threads to release the
37;; read mutex, which allows the (gds-read) here to proceed.
38(define (gds-debug-read)
39 (write-form `(debug-thread-id ,(get-thread-id)))
40 (gds-read))
41
42(define (gds-debug-trap trap-context)
43 "Invoke the GDS debugger to explore the stack at the specified trap."
44 (connect-to-gds)
45 (start-stack 'debugger
46 (let* ((stack (tc:stack trap-context))
47 (flags1 (let ((trap-type (tc:type trap-context)))
48 (case trap-type
49 ((#:return #:error)
50 (list trap-type
51 (tc:return-value trap-context)))
52 (else
53 (list trap-type)))))
54 (flags (if (tc:continuation trap-context)
55 (cons #:continuable flags1)
56 flags1))
57 (fired-traps (tc:fired-traps trap-context))
58 (special-index (and (= (length fired-traps) 1)
59 (is-a? (car fired-traps) <exit-trap>)
60 (eq? (tc:type trap-context) #:return)
61 (- (tc:depth trap-context)
62 (slot-ref (car fired-traps) 'depth)))))
63 ;; Write current stack to the frontend.
64 (write-form (list 'stack
b1860cb3
NJ
65 (if (and special-index (> special-index 0))
66 special-index
67 0)
ea19f0b3
NJ
68 (stack->emacs-readable stack)
69 (append (flags->emacs-readable flags)
70 (slot-ref trap-context
71 'handler-return-syms))))
72 ;; Now wait for instruction.
73 (let loop ((protocol (gds-debug-read)))
74 ;; Act on it.
75 (case (car protocol)
76 ((tweak)
77 ;; Request to tweak the handler return value.
78 (let ((tweaking (catch #t
79 (lambda ()
80 (list (with-input-from-string
81 (cadr protocol)
82 read)))
83 (lambda ignored #f))))
84 (if tweaking
85 (slot-set! trap-context
86 'handler-return-value
87 (cons 'instead (car tweaking)))))
88 (loop (gds-debug-read)))
89 ((continue)
90 ;; Continue (by exiting the debugger).
91 *unspecified*)
92 ((evaluate)
93 ;; Evaluate expression in specified frame.
94 (eval-in-frame stack (cadr protocol) (caddr protocol))
95 (loop (gds-debug-read)))
96 ((info-frame)
97 ;; Return frame info.
98 (let ((frame (stack-ref stack (cadr protocol))))
99 (write-form (list 'info-result
100 (with-output-to-string
101 (lambda ()
102 (write-frame-long frame))))))
103 (loop (gds-debug-read)))
104 ((info-args)
105 ;; Return frame args.
106 (let ((frame (stack-ref stack (cadr protocol))))
107 (write-form (list 'info-result
108 (with-output-to-string
109 (lambda ()
110 (write-frame-args-long frame))))))
111 (loop (gds-debug-read)))
112 ((proc-source)
113 ;; Show source of application procedure.
114 (let* ((frame (stack-ref stack (cadr protocol)))
115 (proc (frame-procedure frame))
116 (source (and proc (procedure-source proc))))
117 (write-form (list 'info-result
118 (if source
119 (sans-surrounding-whitespace
120 (with-output-to-string
121 (lambda ()
122 (pretty-print source))))
123 (if proc
124 "This procedure is coded in C"
125 "This frame has no procedure")))))
126 (loop (gds-debug-read)))
127 ((traps-here)
128 ;; Show the traps that fired here.
129 (write-form (list 'info-result
130 (with-output-to-string
131 (lambda ()
132 (for-each describe
133 (tc:fired-traps trap-context))))))
134 (loop (gds-debug-read)))
135 ((step-into)
136 ;; Set temporary breakpoint on next trap.
137 (at-step gds-debug-trap
138 1
139 #f
140 (if (memq #:return flags)
141 #f
142 (- (stack-length stack)
143 (cadr protocol)))))
144 ((step-over)
145 ;; Set temporary breakpoint on exit from
146 ;; specified frame.
147 (at-exit (- (stack-length stack) (cadr protocol))
148 gds-debug-trap))
149 ((step-file)
150 ;; Set temporary breakpoint on next trap in same
151 ;; source file.
152 (at-step gds-debug-trap
153 1
154 (frame-file-name (stack-ref stack
155 (cadr protocol)))
156 (if (memq #:return flags)
157 #f
158 (- (stack-length stack)
159 (cadr protocol)))))
160 (else
161 (safely-handle-nondebug-protocol protocol)
162 (loop (gds-debug-read))))))))
163
169ccff5 164(define (connect-to-gds . application-name)
ea19f0b3
NJ
165 (or gds-port
166 (begin
167 (set! gds-port
e2d23cc0
NJ
168 (or (let ((s (socket PF_INET SOCK_STREAM 0))
169 (SOL_TCP 6)
170 (TCP_NODELAY 1))
171 (setsockopt s SOL_TCP TCP_NODELAY 1)
172 (catch #t
173 (lambda ()
174 (connect s AF_INET (inet-aton "127.0.0.1") 8333)
175 s)
176 (lambda _ #f)))
177 (let ((s (socket PF_UNIX SOCK_STREAM 0)))
178 (catch #t
179 (lambda ()
180 (connect s AF_UNIX "/tmp/.gds_socket")
181 s)
182 (lambda _ #f)))
183 (error "Couldn't connect to GDS by TCP or Unix domain socket")))
169ccff5
NJ
184 (write-form (list 'name (getpid) (apply client-name application-name))))))
185
186(define (client-name . application-name)
187 (let loop ((args (append application-name (program-arguments))))
188 (if (null? args)
189 (format #f "PID ~A" (getpid))
190 (let ((arg (car args)))
191 (cond ((string-match "^(.*[/\\])?guile(\\..*)?$" arg)
192 (loop (cdr args)))
193 ((string-match "^-" arg)
194 (loop (cdr args)))
195 (else
196 (format #f "~A (PID ~A)" arg (getpid))))))))
ea19f0b3 197
248ee86a
NJ
198;;(if (not (defined? 'make-mutex))
199;; (begin
200;; (define (make-mutex) #f)
201;; (define lock-mutex noop)
202;; (define unlock-mutex noop)))
ea19f0b3
NJ
203
204(define write-mutex (make-mutex))
205
206(define (write-form form)
207 ;; Write any form FORM to GDS.
208 (lock-mutex write-mutex)
209 (write form gds-port)
210 (newline gds-port)
211 (force-output gds-port)
212 (unlock-mutex write-mutex))
213
214(define (stack->emacs-readable stack)
215 ;; Return Emacs-readable representation of STACK.
216 (map (lambda (index)
217 (frame->emacs-readable (stack-ref stack index)))
218 (iota (min (stack-length stack)
219 (cadr (memq 'depth (debug-options)))))))
220
221(define (frame->emacs-readable frame)
222 ;; Return Emacs-readable representation of FRAME.
223 (if (frame-procedure? frame)
224 (list 'application
225 (with-output-to-string
226 (lambda ()
227 (display (if (frame-real? frame) " " "t "))
228 (write-frame-short/application frame)))
229 (source->emacs-readable frame))
230 (list 'evaluation
231 (with-output-to-string
232 (lambda ()
233 (display (if (frame-real? frame) " " "t "))
234 (write-frame-short/expression frame)))
235 (source->emacs-readable frame))))
236
237(define (source->emacs-readable frame)
238 ;; Return Emacs-readable representation of the filename, line and
239 ;; column source properties of SOURCE.
240 (or (frame->source-position frame) 'nil))
241
242(define (flags->emacs-readable flags)
243 ;; Return Emacs-readable representation of trap FLAGS.
244 (let ((prev #f))
245 (map (lambda (flag)
246 (let ((erf (if (and (keyword? flag)
247 (not (eq? prev #:return)))
248 (keyword->symbol flag)
249 (format #f "~S" flag))))
250 (set! prev flag)
251 erf))
252 flags)))
253
254(define (eval-in-frame stack index expr)
255 (write-form
256 (list 'eval-result
257 (format #f "~S"
258 (catch #t
259 (lambda ()
260 (local-eval (with-input-from-string expr read)
261 (memoized-environment
262 (frame-source (stack-ref stack
263 index)))))
264 (lambda args
265 (cons 'ERROR args)))))))
266
267(set! (behaviour-ordering gds-debug-trap) 100)
268
269;;; Code below here adds support for interaction between the GDS
270;;; client program and the Emacs frontend even when not stopped in the
271;;; debugger.
272
273;; A mutex to control attempts by multiple threads to read protocol
274;; back from the frontend.
275(define gds-read-mutex (make-mutex))
276
277;; Read a protocol instruction from the frontend.
278(define (gds-read)
279 ;; Acquire the read mutex.
280 (lock-mutex gds-read-mutex)
281 ;; Tell the front end something that identifies us as a thread.
282 (write-form `(thread-id ,(get-thread-id)))
283 ;; Now read, then release the mutex and return what was read.
284 (let ((x (catch #t
285 (lambda () (read gds-port))
286 (lambda ignored the-eof-object))))
287 (unlock-mutex gds-read-mutex)
288 x))
289
290(define (gds-accept-input exit-on-continue)
291 ;; If reading from the GDS connection returns EOF, we will throw to
292 ;; this catch.
293 (catch 'server-eof
294 (lambda ()
295 (let loop ((protocol (gds-read)))
296 (if (or (eof-object? protocol)
297 (and exit-on-continue
298 (eq? (car protocol) 'continue)))
299 (throw 'server-eof))
300 (safely-handle-nondebug-protocol protocol)
301 (loop (gds-read))))
302 (lambda ignored #f)))
303
304(define (safely-handle-nondebug-protocol protocol)
305 ;; This catch covers any internal errors in the GDS code or
306 ;; protocol.
307 (catch #t
308 (lambda ()
309 (lazy-catch #t
310 (lambda ()
311 (handle-nondebug-protocol protocol))
312 save-lazy-trap-context-and-rethrow))
313 (lambda (key . args)
314 (write-form
315 `(eval-results (error . ,(format #f "~s" protocol))
316 ,(if last-lazy-trap-context 't 'nil)
317 "GDS Internal Error
318Please report this to <neil@ossau.uklinux.net>, ideally including:
319- a description of the scenario in which this error occurred
320- which versions of Guile and guile-debugging you are using
321- the error stack, which you can get by clicking on the link below,
322 and then cut and paste into your report.
323Thanks!\n\n"
324 ,(list (with-output-to-string
325 (lambda ()
326 (write key)
327 (display ": ")
328 (write args)
329 (newline)))))))))
330
331;; The key that is used to signal a read error changes from 1.6 to
332;; 1.8; here we cover all eventualities by discovering the key
333;; dynamically.
334(define read-error-key
335 (catch #t
336 (lambda ()
337 (with-input-from-string "(+ 3 4" read))
338 (lambda (key . args)
339 key)))
340
341(define (handle-nondebug-protocol protocol)
342 (case (car protocol)
343
344 ((eval)
345 (set! last-lazy-trap-context #f)
091baf9e 346 (apply (lambda (correlator module port-name line column code flags)
ea19f0b3
NJ
347 (with-input-from-string code
348 (lambda ()
349 (set-port-filename! (current-input-port) port-name)
350 (set-port-line! (current-input-port) line)
351 (set-port-column! (current-input-port) column)
352 (let ((m (and module (resolve-module-from-root module))))
353 (catch read-error-key
354 (lambda ()
355 (let loop ((exprs '()) (x (read)))
356 (if (eof-object? x)
357 ;; Expressions to be evaluated have all
358 ;; been read. Now evaluate them.
359 (let loop2 ((exprs (reverse! exprs))
360 (results '())
361 (n 1))
362 (if (null? exprs)
363 (write-form `(eval-results ,correlator
364 ,(if last-lazy-trap-context 't 'nil)
365 ,@results))
366 (loop2 (cdr exprs)
367 (append results (gds-eval (car exprs) m
368 (if (and (null? (cdr exprs))
369 (= n 1))
370 #f n)))
371 (+ n 1))))
372 ;; Another complete expression read; add
373 ;; it to the list.
374 (begin
091baf9e
NJ
375 (if (and (pair? x)
376 (memq 'debug flags))
377 (install-trap (make <source-trap>
378 #:expression x
379 #:behaviour gds-debug-trap)))
ea19f0b3
NJ
380 (loop (cons x exprs) (read))))))
381 (lambda (key . args)
382 (write-form `(eval-results
383 ,correlator
384 ,(if last-lazy-trap-context 't 'nil)
385 ,(with-output-to-string
386 (lambda ()
387 (display ";;; Reading expressions")
388 (display " to evaluate\n")
389 (apply display-error #f
390 (current-output-port) args)))
69986e21 391 ("error-in-read")))))))))
ea19f0b3
NJ
392 (cdr protocol)))
393
394 ((complete)
395 (let ((matches (apropos-internal
396 (string-append "^" (regexp-quote (cadr protocol))))))
397 (cond ((null? matches)
398 (write-form '(completion-result nil)))
399 (else
400 ;;(write matches (current-error-port))
401 ;;(newline (current-error-port))
402 (let ((match
403 (let loop ((match (symbol->string (car matches)))
404 (matches (cdr matches)))
405 ;;(write match (current-error-port))
406 ;;(newline (current-error-port))
407 ;;(write matches (current-error-port))
408 ;;(newline (current-error-port))
409 (if (null? matches)
410 match
411 (if (string-prefix=? match
412 (symbol->string (car matches)))
413 (loop match (cdr matches))
414 (loop (substring match 0
415 (- (string-length match) 1))
416 matches))))))
417 (if (string=? match (cadr protocol))
418 (write-form `(completion-result
419 ,(map symbol->string matches)))
420 (write-form `(completion-result
421 ,match))))))))
422
423 ((debug-lazy-trap-context)
424 (if last-lazy-trap-context
425 (gds-debug-trap last-lazy-trap-context)
426 (error "There is no stack available to show")))
427
ea19f0b3
NJ
428 (else
429 (error "Unexpected protocol:" protocol))))
430
ea19f0b3
NJ
431(define (resolve-module-from-root name)
432 (save-module-excursion
433 (lambda ()
434 (set-current-module the-root-module)
435 (resolve-module name))))
436
437(define (gds-eval x m part)
438 ;; Consumer to accept possibly multiple values and present them for
439 ;; Emacs as a list of strings.
440 (define (value-consumer . values)
441 (if (unspecified? (car values))
442 '()
443 (map (lambda (value)
444 (with-output-to-string (lambda () (write value))))
445 values)))
446 ;; Now do evaluation.
447 (let ((intro (if part
448 (format #f ";;; Evaluating expression ~A" part)
449 ";;; Evaluating"))
450 (value #f))
451 (let* ((do-eval (if m
452 (lambda ()
453 (display intro)
454 (display " in module ")
455 (write (module-name m))
456 (newline)
457 (set! value
458 (call-with-values (lambda ()
459 (start-stack 'gds-eval-stack
460 (eval x m)))
461 value-consumer)))
462 (lambda ()
463 (display intro)
464 (display " in current module ")
465 (write (module-name (current-module)))
466 (newline)
467 (set! value
468 (call-with-values (lambda ()
469 (start-stack 'gds-eval-stack
470 (primitive-eval x)))
471 value-consumer)))))
472 (output
473 (with-output-to-string
474 (lambda ()
475 (catch #t
476 (lambda ()
477 (lazy-catch #t
478 do-eval
479 save-lazy-trap-context-and-rethrow))
480 (lambda (key . args)
481 (case key
482 ((misc-error signal unbound-variable numerical-overflow)
483 (apply display-error #f
484 (current-output-port) args)
485 (set! value '("error-in-evaluation")))
486 (else
487 (display "EXCEPTION: ")
488 (display key)
489 (display " ")
490 (write args)
491 (newline)
492 (set! value
493 '("unhandled-exception-in-evaluation"))))))))))
494 (list output value))))
495
496(define last-lazy-trap-context #f)
497
498(define (save-lazy-trap-context-and-rethrow key . args)
499 (set! last-lazy-trap-context
500 (throw->trap-context key args save-lazy-trap-context-and-rethrow))
501 (apply throw key args))
502
503(define (run-utility)
69986e21 504 (connect-to-gds)
ea19f0b3
NJ
505 (write (getpid))
506 (newline)
507 (force-output)
508 (named-module-use! '(guile-user) '(ice-9 session))
509 (gds-accept-input #f))
510
ea19f0b3
NJ
511(define-method (trap-description (trap <trap>))
512 (let loop ((description (list (class-name (class-of trap))))
513 (next 'installed?))
514 (case next
515 ((installed?)
516 (loop (if (slot-ref trap 'installed)
517 (cons 'installed description)
518 description)
519 'conditional?))
520 ((conditional?)
521 (loop (if (slot-ref trap 'condition)
522 (cons 'conditional description)
523 description)
524 'skip-count))
525 ((skip-count)
526 (loop (let ((skip-count (slot-ref trap 'skip-count)))
527 (if (zero? skip-count)
528 description
529 (cons* skip-count 'skip-count description)))
530 'single-shot?))
531 ((single-shot?)
532 (loop (if (slot-ref trap 'single-shot)
533 (cons 'single-shot description)
534 description)
535 'done))
536 (else
537 (reverse! description)))))
538
539(define-method (trap-description (trap <procedure-trap>))
540 (let ((description (next-method)))
541 (set-cdr! description
542 (cons (procedure-name (slot-ref trap 'procedure))
543 (cdr description)))
544 description))
545
546(define-method (trap-description (trap <source-trap>))
547 (let ((description (next-method)))
548 (set-cdr! description
549 (cons (format #f "~s" (slot-ref trap 'expression))
550 (cdr description)))
551 description))
552
553(define-method (trap-description (trap <location-trap>))
554 (let ((description (next-method)))
555 (set-cdr! description
556 (cons* (slot-ref trap 'file-regexp)
557 (slot-ref trap 'line)
558 (slot-ref trap 'column)
559 (cdr description)))
560 description))
561
562(define (gds-trace-trap trap-context)
563 (connect-to-gds)
564 (gds-do-trace trap-context)
565 (at-exit (tc:depth trap-context) gds-do-trace))
566
567(define (gds-do-trace trap-context)
568 (write-form (list 'trace
569 (format #f
570 "~3@a: ~a"
571 (trace/stack-real-depth trap-context)
572 (trace/info trap-context)))))
573
574(define (gds-trace-subtree trap-context)
575 (connect-to-gds)
576 (gds-do-trace trap-context)
577 (let ((step-trap (make <step-trap> #:behaviour gds-do-trace)))
578 (install-trap step-trap)
579 (at-exit (tc:depth trap-context)
580 (lambda (trap-context)
581 (uninstall-trap step-trap)))))
582
583;;; (ice-9 gds-client) ends here.