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