home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / guile / 1.8 / ice-9 / debugging / traps.scm < prev    next >
Encoding:
Text File  |  2008-12-17  |  38.4 KB  |  1,038 lines

  1. ;;;; (ice-9 debugging traps) -- abstraction of libguile's traps interface
  2.  
  3. ;;; Copyright (C) 2002, 2004 Free Software Foundation, Inc.
  4. ;;; Copyright (C) 2005 Neil Jerram
  5. ;;;
  6. ;; This library is free software; you can redistribute it and/or
  7. ;; modify it under the terms of the GNU Lesser General Public
  8. ;; License as published by the Free Software Foundation; either
  9. ;; version 2.1 of the License, or (at your option) any later version.
  10. ;; 
  11. ;; This library is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14. ;; Lesser General Public License for more details.
  15. ;; 
  16. ;; You should have received a copy of the GNU Lesser General Public
  17. ;; License along with this library; if not, write to the Free Software
  18. ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  19.  
  20. ;;; This module provides an abstraction around Guile's low level trap
  21. ;;; handler interface; its aim is to make the low level trap mechanism
  22. ;;; shareable between the debugger and other applications, and to
  23. ;;; insulate the rest of the debugger code a bit from changes that may
  24. ;;; occur in the low level trap interface in future.
  25.  
  26. (define-module (ice-9 debugging traps)
  27.   #:use-module (ice-9 regex)
  28.   #:use-module (oop goops)
  29.   #:use-module (oop goops describe)
  30.   #:use-module (ice-9 debugging trc)
  31.   #:use-module (srfi srfi-1)
  32.   #:use-module (srfi srfi-2)
  33.   #:export (tc:type
  34.             tc:continuation
  35.             tc:expression
  36.             tc:return-value
  37.             tc:stack
  38.             tc:frame
  39.             tc:depth
  40.             tc:real-depth
  41.             tc:exit-depth
  42.         tc:fired-traps
  43.         ;; Interface for users of <trap> subclasses defined in
  44.         ;; this module.
  45.             add-trapped-stack-id!
  46.         remove-trapped-stack-id!
  47.         <procedure-trap>
  48.         <exit-trap>
  49.         <entry-trap>
  50.         <apply-trap>
  51.         <step-trap>
  52.         <source-trap>
  53.         <location-trap>
  54.         install-trap
  55.         uninstall-trap
  56.             all-traps
  57.             get-trap
  58.         list-traps
  59.         trap-ordering
  60.             behaviour-ordering
  61.         throw->trap-context
  62.         on-lazy-handler-dispatch
  63.         ;; Interface for authors of new <trap> subclasses.
  64.         <trap-context>
  65.         <trap>
  66.         trap->behaviour
  67.         trap-runnable?
  68.         install-apply-frame-trap
  69.         install-breakpoint-trap
  70.         install-enter-frame-trap
  71.         install-exit-frame-trap
  72.         install-trace-trap
  73.         uninstall-apply-frame-trap
  74.         uninstall-breakpoint-trap
  75.         uninstall-enter-frame-trap
  76.         uninstall-exit-frame-trap
  77.         uninstall-trace-trap
  78.         frame->source-position
  79.         frame-file-name
  80.         without-traps
  81.             guile-trap-features)
  82.   #:re-export (make)
  83.   #:export-syntax (trap-here))
  84.  
  85. ;; How to debug the debugging infrastructure, when needed.  Grep for
  86. ;; "(trc " to find other symbols that can be passed to trc-add.
  87. ;; (trc-add 'after-gc-hook)
  88.  
  89. ;; In Guile 1.7 onwards, weak-vector and friends are provided by the
  90. ;; (ice-9 weak-vector) module.
  91. (cond ((string>=? (version) "1.7")
  92.        (use-modules (ice-9 weak-vector))))
  93.  
  94. ;;; The current low level traps interface is as follows.
  95. ;;;
  96. ;;; All trap handlers are subject to SCM_TRAPS_P, which is controlled
  97. ;;; by the `traps' setting of `(evaluator-traps-interface)' but also
  98. ;;; (and more relevant in most cases) by the `with-traps' procedure.
  99. ;;; Basically, `with-traps' sets SCM_TRAPS_P to 1 during execution of
  100. ;;; its thunk parameter.
  101. ;;;
  102. ;;; Note that all trap handlers are called with SCM_TRAPS_P set to 0
  103. ;;; for the duration of the call, to avoid nasty recursive trapping
  104. ;;; loops.  If a trap handler knows what it is doing, it can override
  105. ;;; this by `(trap-enable traps)'.
  106. ;;;
  107. ;;; The apply-frame handler is called when Guile is about to perform
  108. ;;; an application if EITHER the `apply-frame' evaluator trap option
  109. ;;; is set, OR the `trace' debug option is set and the procedure to
  110. ;;; apply has its `trace' procedure property set.  The arguments
  111. ;;; passed are:
  112. ;;;
  113. ;;; - the symbol 'apply-frame
  114. ;;;
  115. ;;; - a continuation or debug object describing the current stack
  116. ;;;
  117. ;;; - a boolean indicating whether the application is tail-recursive.
  118. ;;;
  119. ;;; The enter-frame handler is called when the evaluator begins a new
  120. ;;; evaluation frame if EITHER the `enter-frame' evaluator trap option
  121. ;;; is set, OR the `breakpoints' debug option is set and the code to
  122. ;;; be evaluated has its `breakpoint' source property set.  The
  123. ;;; arguments passed are:
  124. ;;;
  125. ;;; - the symbol 'enter-frame
  126. ;;;
  127. ;;; - a continuation or debug object describing the current stack
  128. ;;;
  129. ;;; - a boolean indicating whether the application is tail-recursive.
  130. ;;;
  131. ;;; - an unmemoized copy of the expression to be evaluated.
  132. ;;;
  133. ;;; If the `enter-frame' evaluator trap option is set, the enter-frame
  134. ;;; handler is also called when about to perform an application in
  135. ;;; SCM_APPLY, immediately before possibly calling the apply-frame
  136. ;;; handler.  (I don't totally understand this.)  In this case, the
  137. ;;; arguments passed are:
  138. ;;;
  139. ;;; - the symbol 'enter-frame
  140. ;;;
  141. ;;; - a continuation or debug object describing the current stack.
  142. ;;;
  143. ;;; The exit-frame handler is called when Guile exits an evaluation
  144. ;;; frame (in SCM_CEVAL) or an application frame (in SCM_APPLY), if
  145. ;;; EITHER the `exit-frame' evaluator trap option is set, OR the
  146. ;;; `trace' debug option is set and the frame is marked as having been
  147. ;;; traced.  The frame will be marked as having been traced if the
  148. ;;; apply-frame handler was called for this frame.  (This is trickier
  149. ;;; than it sounds because of tail recursion: the same debug frame
  150. ;;; could have been used for multiple applications, only some of which
  151. ;;; were traced - I think.)  The arguments passed are:
  152. ;;;
  153. ;;; - the symbol 'exit-frame
  154. ;;;
  155. ;;; - a continuation or debug object describing the current stack
  156. ;;;
  157. ;;; - the result of the evaluation or application.
  158.  
  159. ;;; {Trap Context}
  160. ;;;
  161. ;;; A trap context is a GOOPS object that encapsulates all the useful
  162. ;;; information about a particular trap.  Encapsulating this
  163. ;;; information in a single object also allows us:
  164. ;;;
  165. ;;; - to defer the calculation of information that is time-consuming
  166. ;;; to calculate, such as the stack, and to cache such information so
  167. ;;; that it is only ever calculated once per trap
  168. ;;;
  169. ;;; - to pass all interesting information to trap behaviour procedures
  170. ;;; in a single parameter, which (i) is convenient and (ii) makes for
  171. ;;; a more future-proof interface.
  172. ;;;
  173. ;;; It also allows us - where very carefully documented! - to pass
  174. ;;; information from one behaviour procedure to another.
  175.  
  176. (define-class <trap-context> ()
  177.   ;; Information provided directly by the trap calls from the
  178.   ;; evaluator.  The "type" slot holds a keyword indicating the type
  179.   ;; of the trap: one of #:evaluation, #:application, #:return,
  180.   ;; #:error.
  181.   (type #:getter tc:type
  182.         #:init-keyword #:type)
  183.   ;; The "continuation" slot holds the continuation (or debug object,
  184.   ;; if "cheap" traps are enabled, which is the default) at the point
  185.   ;; of the trap.  For an error trap it is #f.
  186.   (continuation #:getter tc:continuation
  187.                 #:init-keyword #:continuation)
  188.   ;; The "expression" slot holds the source code expression, for an
  189.   ;; evaluation trap.
  190.   (expression #:getter tc:expression
  191.               #:init-keyword #:expression
  192.               #:init-value #f)
  193.   ;; The "return-value" slot holds the return value, for a return
  194.   ;; trap, or the error args, for an error trap.
  195.   (return-value #:getter tc:return-value
  196.                 #:init-keyword #:return-value
  197.                 #:init-value #f)
  198.   ;; The list of trap objects which fired in this trap context.
  199.   (fired-traps #:getter tc:fired-traps
  200.            #:init-value '())
  201.   ;; The set of symbols which, if one of them is set in the CAR of the
  202.   ;; handler-return-value slot, will cause the CDR of that slot to
  203.   ;; have an effect.
  204.   (handler-return-syms #:init-value '())
  205.   ;; The value which the trap handler should return to the evaluator.
  206.   (handler-return-value #:init-value #f)
  207.   ;; Calculated and cached information.  "stack" is the stack
  208.   ;; (computed from the continuation (or debug object) by make-stack,
  209.   ;; or else (in the case of an error trap) by (make-stack #t ...).
  210.   (stack #:init-value #f)
  211.   (frame #:init-value #f)
  212.   (depth #:init-value #f)
  213.   (real-depth #:init-value #f)
  214.   (exit-depth #:init-value #f))
  215.  
  216. (define-method (tc:stack (ctx <trap-context>))
  217.   (or (slot-ref ctx 'stack)
  218.       (let ((stack (make-stack (tc:continuation ctx))))
  219.         (slot-set! ctx 'stack stack)
  220.         stack)))
  221.  
  222. (define-method (tc:frame (ctx <trap-context>))
  223.   (or (slot-ref ctx 'frame)
  224.       (let ((frame (cond ((tc:continuation ctx) => last-stack-frame)
  225.              (else (stack-ref (tc:stack ctx) 0)))))
  226.         (slot-set! ctx 'frame frame)
  227.         frame)))
  228.  
  229. (define-method (tc:depth (ctx <trap-context>))
  230.   (or (slot-ref ctx 'depth)
  231.       (let ((depth (stack-length (tc:stack ctx))))
  232.         (slot-set! ctx 'depth depth)
  233.         depth)))
  234.  
  235. (define-method (tc:real-depth (ctx <trap-context>))
  236.   (or (slot-ref ctx 'real-depth)
  237.       (let* ((stack (tc:stack ctx))
  238.          (real-depth (apply +
  239.                 (map (lambda (i)
  240.                        (if (frame-real? (stack-ref stack i))
  241.                        1
  242.                        0))
  243.                      (iota (tc:depth ctx))))))
  244.         (slot-set! ctx 'real-depth real-depth)
  245.         real-depth)))
  246.  
  247. (define-method (tc:exit-depth (ctx <trap-context>))
  248.   (or (slot-ref ctx 'exit-depth)
  249.       (let* ((stack (tc:stack ctx))
  250.          (depth (tc:depth ctx))
  251.          (exit-depth (let loop ((exit-depth depth))
  252.                (if (or (zero? exit-depth)
  253.                    (frame-real? (stack-ref stack
  254.                                (- depth
  255.                                   exit-depth))))
  256.                    exit-depth
  257.                    (loop (- exit-depth 1))))))
  258.     (slot-set! ctx 'exit-depth exit-depth)
  259.         exit-depth)))
  260.  
  261. ;;; {Stack IDs}
  262. ;;;
  263. ;;; Mechanism for limiting trapping to contexts whose stack ID matches
  264. ;;; one of a registered set.  The default is for traps to fire
  265. ;;; regardless of stack ID.
  266.  
  267. (define trapped-stack-ids (list #t))
  268. (define all-stack-ids-trapped? #t)
  269.  
  270. (define (add-trapped-stack-id! id)
  271.   "Add ID to the set of stack ids for which traps are active.
  272. If `#t' is in this set, traps are active regardless of stack context.
  273. To remove ID again, use `remove-trapped-stack-id!'.  If you add the
  274. same ID twice using `add-trapped-stack-id!', you will need to remove
  275. it twice."
  276.   (set! trapped-stack-ids (cons id trapped-stack-ids))
  277.   (set! all-stack-ids-trapped? (memq #t trapped-stack-ids)))
  278.  
  279. (define (remove-trapped-stack-id! id)
  280.   "Remove ID from the set of stack ids for which traps are active."
  281.   (set! trapped-stack-ids (delq1! id trapped-stack-ids))
  282.   (set! all-stack-ids-trapped? (memq #t trapped-stack-ids)))
  283.  
  284. (define (trap-here? cont)
  285.   ;; Return true if the stack id of the specified continuation (or
  286.   ;; debug object) is in the set that we should trap for; otherwise
  287.   ;; false.
  288.   (or all-stack-ids-trapped?
  289.       (memq (stack-id cont) trapped-stack-ids)))
  290.  
  291. ;;; {Global State}
  292. ;;;
  293. ;;; Variables tracking registered handlers, relevant procedures, and
  294. ;;; what's turned on as regards the evaluator's debugging options.
  295.  
  296. (define enter-frame-traps '())
  297. (define apply-frame-traps '())
  298. (define exit-frame-traps '())
  299. (define breakpoint-traps '())
  300. (define trace-traps '())
  301.  
  302. (define (non-null? hook)
  303.   (not (null? hook)))
  304.  
  305. ;; The low level frame handlers must all be initialized to something
  306. ;; harmless.  Otherwise we hit a problem immediately when trying to
  307. ;; enable one of these handlers.
  308. (trap-set! enter-frame-handler noop)
  309. (trap-set! apply-frame-handler noop)
  310. (trap-set! exit-frame-handler noop)
  311.  
  312. (define set-debug-and-trap-options
  313.   (let ((dopts (debug-options))
  314.     (topts (evaluator-traps-interface))
  315.     (setting (lambda (key opts)
  316.            (let ((l (memq key opts)))
  317.              (and l
  318.               (not (null? (cdr l)))
  319.               (cadr l)))))
  320.     (debug-set-boolean! (lambda (key value)
  321.                   ((if value debug-enable debug-disable) key)))
  322.     (trap-set-boolean! (lambda (key value)
  323.                  ((if value trap-enable trap-disable) key))))
  324.     (let ((save-debug (memq 'debug dopts))
  325.       (save-trace (memq 'trace dopts))
  326.       (save-breakpoints (memq 'breakpoints dopts))
  327.       (save-enter-frame (memq 'enter-frame topts))
  328.       (save-apply-frame (memq 'apply-frame topts))
  329.       (save-exit-frame (memq 'exit-frame topts))
  330.       (save-enter-frame-handler (setting 'enter-frame-handler topts))
  331.       (save-apply-frame-handler (setting 'apply-frame-handler topts))
  332.       (save-exit-frame-handler (setting 'exit-frame-handler topts)))
  333.       (lambda ()
  334.     (let ((need-trace (non-null? trace-traps))
  335.           (need-breakpoints (non-null? breakpoint-traps))
  336.           (need-enter-frame (non-null? enter-frame-traps))
  337.           (need-apply-frame (non-null? apply-frame-traps))
  338.           (need-exit-frame (non-null? exit-frame-traps)))
  339.       (debug-set-boolean! 'debug
  340.                   (or need-trace
  341.                   need-breakpoints
  342.                   need-enter-frame
  343.                   need-apply-frame
  344.                   need-exit-frame
  345.                   save-debug))
  346.       (debug-set-boolean! 'trace
  347.                   (or need-trace
  348.                   save-trace))
  349.       (debug-set-boolean! 'breakpoints
  350.                   (or need-breakpoints
  351.                   save-breakpoints))
  352.       (trap-set-boolean! 'enter-frame
  353.                  (or need-enter-frame
  354.                  save-enter-frame))
  355.       (trap-set-boolean! 'apply-frame
  356.                  (or need-apply-frame
  357.                  save-apply-frame))
  358.       (trap-set-boolean! 'exit-frame
  359.                  (or need-exit-frame
  360.                  save-exit-frame))
  361.       (trap-set! enter-frame-handler
  362.              (cond ((or need-breakpoints
  363.                 need-enter-frame)
  364.                 enter-frame-handler)
  365.                (else save-enter-frame-handler)))
  366.       (trap-set! apply-frame-handler
  367.              (cond ((or need-trace
  368.                 need-apply-frame)
  369.                 apply-frame-handler)
  370.                (else save-apply-frame-handler)))
  371.       (trap-set! exit-frame-handler
  372.              (cond ((or need-exit-frame)
  373.                 exit-frame-handler)
  374.                (else save-exit-frame-handler))))
  375.     ;;(write (evaluator-traps-interface))
  376.     *unspecified*))))
  377.  
  378. (define (enter-frame-handler key cont . args)
  379.   ;; For a non-application entry, ARGS is (TAIL? EXP), where EXP is an
  380.   ;; unmemoized copy of the source expression.  For an application
  381.   ;; entry, ARGS is empty.
  382.   (if (trap-here? cont)
  383.       (let* ((application-entry? (null? args))
  384.              (trap-context (make <trap-context>
  385.                              #:type #:evaluation
  386.                              #:continuation cont
  387.                              #:expression (if application-entry?
  388.                                               #f
  389.                                               (cadr args)))))
  390.     (trc 'enter-frame-handler)
  391.     (if (and (not application-entry?)
  392.                  (memq 'tweaking guile-trap-features))
  393.         (slot-set! trap-context 'handler-return-syms '(instead)))
  394.         (run-traps (if application-entry?
  395.                enter-frame-traps
  396.                (append enter-frame-traps breakpoint-traps))
  397.            trap-context)
  398.     (slot-ref trap-context 'handler-return-value))))
  399.  
  400. (define (apply-frame-handler key cont tail?)
  401.   (if (trap-here? cont)
  402.       (let ((trap-context (make <trap-context>
  403.                             #:type #:application
  404.                             #:continuation cont)))
  405.     (trc 'apply-frame-handler tail?)
  406.         (run-traps (append apply-frame-traps trace-traps) trap-context)
  407.     (slot-ref trap-context 'handler-return-value))))
  408.  
  409. (define (exit-frame-handler key cont retval)
  410.   (if (trap-here? cont)
  411.       (let ((trap-context (make <trap-context>
  412.                             #:type #:return
  413.                             #:continuation cont
  414.                             #:return-value retval)))
  415.     (trc 'exit-frame-handler retval (tc:depth trap-context))
  416.     (if (memq 'tweaking guile-trap-features)
  417.             (slot-set! trap-context 'handler-return-syms '(instead)))
  418.         (run-traps exit-frame-traps trap-context)
  419.     (slot-ref trap-context 'handler-return-value))))
  420.  
  421. (define-macro (trap-installer trap-list)
  422.   `(lambda (trap)
  423.      (set! ,trap-list (cons trap ,trap-list))
  424.      (set-debug-and-trap-options)))
  425.  
  426. (define install-enter-frame-trap (trap-installer enter-frame-traps))
  427. (define install-apply-frame-trap (trap-installer apply-frame-traps))
  428. (define install-exit-frame-trap (trap-installer exit-frame-traps))
  429. (define install-breakpoint-trap (trap-installer breakpoint-traps))
  430. (define install-trace-trap (trap-installer trace-traps))
  431.  
  432. (define-macro (trap-uninstaller trap-list)
  433.   `(lambda (trap)
  434.      (or (memq trap ,trap-list)
  435.          (error "Trap list does not include the specified trap"))
  436.      (set! ,trap-list (delq1! trap ,trap-list))
  437.      (set-debug-and-trap-options)))
  438.  
  439. (define uninstall-enter-frame-trap (trap-uninstaller enter-frame-traps))
  440. (define uninstall-apply-frame-trap (trap-uninstaller apply-frame-traps))
  441. (define uninstall-exit-frame-trap (trap-uninstaller exit-frame-traps))
  442. (define uninstall-breakpoint-trap (trap-uninstaller breakpoint-traps))
  443. (define uninstall-trace-trap (trap-uninstaller trace-traps))
  444.  
  445. (define trap-ordering (make-object-property))
  446. (define behaviour-ordering (make-object-property))
  447.  
  448. (define (run-traps traps trap-context)
  449.   (let ((behaviours (apply append
  450.                (map (lambda (trap)
  451.                   (trap->behaviour trap trap-context))
  452.                 (sort traps
  453.                       (lambda (t1 t2)
  454.                     (< (or (trap-ordering t1) 0)
  455.                        (or (trap-ordering t2) 0))))))))
  456.     (for-each (lambda (proc)
  457.         (proc trap-context))
  458.           (sort (delete-duplicates behaviours)
  459.             (lambda (b1 b2)
  460.                     (< (or (behaviour-ordering b1) 0)
  461.                        (or (behaviour-ordering b2) 0)))))))
  462.  
  463. ;;; {Pseudo-Traps for Non-Trap Events}
  464.  
  465. ;;; Once there is a body of code to do with responding to (debugging,
  466. ;;; tracing, etc.) traps, it makes sense to be able to leverage that
  467. ;;; same code for certain events that are trap-like, but not actually
  468. ;;; traps in the sense of the calls made by libguile's evaluator.
  469.  
  470. ;;; The main example of this is when an error is signalled.  Guile
  471. ;;; doesn't yet have a 100% reliable way of hooking into errors, but
  472. ;;; in practice most errors go through a lazy-catch whose handler is
  473. ;;; lazy-handler-dispatch (defined in ice-9/boot-9.scm), which in turn
  474. ;;; calls default-lazy-handler.  So we can present most errors as
  475. ;;; pseudo-traps by modifying default-lazy-handler.
  476.  
  477. (define default-default-lazy-handler default-lazy-handler)
  478.  
  479. (define (throw->trap-context key args . stack-args)
  480.   (let ((ctx (make <trap-context>
  481.            #:type #:error
  482.            #:continuation #f
  483.            #:return-value (cons key args))))
  484.     (slot-set! ctx 'stack
  485.            (let ((caller-stack (and (= (length stack-args) 1)
  486.                     (car stack-args))))
  487.          (if (stack? caller-stack)
  488.              caller-stack
  489.              (apply make-stack #t stack-args))))
  490.     ctx))
  491.  
  492. (define (on-lazy-handler-dispatch behaviour . ignored-keys)
  493.   (set! default-lazy-handler
  494.     (if behaviour
  495.         (lambda (key . args)
  496.           (or (memq key ignored-keys)
  497.           (behaviour (throw->trap-context key
  498.                           args
  499.                           lazy-handler-dispatch)))
  500.           (apply default-default-lazy-handler key args))
  501.         default-default-lazy-handler)))
  502.  
  503. ;;; {Trap Classes}
  504.  
  505. ;;; Class: <trap>
  506. ;;;
  507. ;;; <trap> is the base class for traps.  Any actual trap should be an
  508. ;;; instance of a class derived from <trap>, not of <trap> itself,
  509. ;;; because there is no base class method for the install-trap,
  510. ;;; trap-runnable? and uninstall-trap GFs.
  511. (define-class <trap> ()
  512.   ;; "number" slot: the number of this trap (assigned automatically).
  513.   (number)
  514.   ;; "installed" slot: whether this trap is installed.
  515.   (installed #:init-value #f)
  516.   ;; "condition" slot: if non-#f, this is a thunk which is called when
  517.   ;; the trap fires, to determine whether trap processing should
  518.   ;; proceed any further.
  519.   (condition #:init-value #f #:init-keyword #:condition)
  520.   ;; "skip-count" slot: a count of valid (after "condition"
  521.   ;; processing) firings of this trap to skip.
  522.   (skip-count #:init-value 0 #:init-keyword #:skip-count)
  523.   ;; "single-shot" slot: if non-#f, this trap is removed after it has
  524.   ;; successfully fired (after "condition" and "skip-count"
  525.   ;; processing) for the first time.
  526.   (single-shot #:init-value #f #:init-keyword #:single-shot)
  527.   ;; "behaviour" slot: procedure or list of procedures to call
  528.   ;; (passing the trap context as parameter) if we finally decide
  529.   ;; (after "condition" and "skip-count" processing) to run this
  530.   ;; trap's behaviour.
  531.   (behaviour #:init-value '() #:init-keyword #:behaviour)
  532.   ;; "repeat-identical-behaviour" slot: normally, if multiple <trap>
  533.   ;; objects are triggered by the same low level trap, and they
  534.   ;; request the same behaviour, it's only useful to do that behaviour
  535.   ;; once (per low level trap); so by default multiple requests for
  536.   ;; the same behaviour are coalesced.  If this slot is non-#f, the
  537.   ;; contents of the "behaviour" slot are uniquified so that they
  538.   ;; avoid being coalesced in this way.
  539.   (repeat-identical-behaviour #:init-value #f
  540.                   #:init-keyword #:repeat-identical-behaviour)
  541.   ;; "observer" slot: this is a procedure that is called with one
  542.   ;; EVENT argument when the trap status changes in certain
  543.   ;; interesting ways, currently the following.  (1) When the trap is
  544.   ;; uninstalled because of the target becoming inaccessible; EVENT in
  545.   ;; this case is 'target-gone.
  546.   (observer #:init-value #f #:init-keyword #:observer))
  547.  
  548. (define last-assigned-trap-number 0)
  549. (define all-traps (make-weak-value-hash-table 7))
  550.  
  551. (define-method (initialize (trap <trap>) initargs)
  552.   (next-method)
  553.   ;; Assign a trap number, and store in the hash of all traps.
  554.   (set! last-assigned-trap-number (+ last-assigned-trap-number 1))
  555.   (slot-set! trap 'number last-assigned-trap-number)
  556.   (hash-set! all-traps last-assigned-trap-number trap)
  557.   ;; Listify the behaviour slot, if not a list already.
  558.   (let ((behaviour (slot-ref trap 'behaviour)))
  559.     (if (procedure? behaviour)
  560.     (slot-set! trap 'behaviour (list behaviour)))))
  561.  
  562. (define-generic install-trap)        ; provided mostly by subclasses
  563. (define-generic uninstall-trap)        ; provided mostly by subclasses
  564. (define-generic trap->behaviour)    ; provided by <trap>
  565. (define-generic trap-runnable?)        ; provided by subclasses
  566.  
  567. (define-method (install-trap (trap <trap>))
  568.   (if (slot-ref trap 'installed)
  569.       (error "Trap is already installed"))
  570.   (slot-set! trap 'installed #t))
  571.  
  572. (define-method (uninstall-trap (trap <trap>))
  573.   (or (slot-ref trap 'installed)
  574.       (error "Trap is not installed"))
  575.   (slot-set! trap 'installed #f))
  576.  
  577. ;;; uniquify-behaviour
  578. ;;;
  579. ;;; Uniquify BEHAVIOUR by wrapping it in a new lambda.
  580. (define (uniquify-behaviour behaviour)
  581.   (lambda (trap-context)
  582.     (behaviour trap-context)))
  583.  
  584. ;;; trap->behaviour
  585. ;;;
  586. ;;; If TRAP is runnable, given TRAP-CONTEXT, return a list of
  587. ;;; behaviour procs to call with TRAP-CONTEXT as a parameter.
  588. ;;; Otherwise return the empty list.
  589. (define-method (trap->behaviour (trap <trap>) (trap-context <trap-context>))
  590.   (if (and
  591.        ;; Check that the trap is runnable.  Runnability is implemented
  592.        ;; by the subclass and allows us to check, for example, that
  593.        ;; the procedure being applied in an apply-frame trap matches
  594.        ;; this trap's procedure.
  595.        (trap-runnable? trap trap-context)
  596.        ;; Check the additional condition, if specified.
  597.        (let ((condition (slot-ref trap 'condition)))
  598.      (or (not condition)
  599.          ((condition))))
  600.        ;; Check for a skip count.
  601.        (let ((skip-count (slot-ref trap 'skip-count)))
  602.      (if (zero? skip-count)
  603.          #t
  604.          (begin
  605.            (slot-set! trap 'skip-count (- skip-count 1))
  606.            #f))))
  607.       ;; All checks passed, so we will return the contents of this
  608.       ;; trap's behaviour slot.
  609.       (begin
  610.     ;; First, though, remove this trap if its single-shot slot
  611.     ;; indicates that it should fire only once.
  612.     (if (slot-ref trap 'single-shot)
  613.         (uninstall-trap trap))
  614.     ;; Add this trap object to the context's list of traps which
  615.     ;; fired here.
  616.     (slot-set! trap-context 'fired-traps
  617.            (cons trap (tc:fired-traps trap-context)))
  618.     ;; Return trap behaviour, uniquified if necessary.
  619.     (if (slot-ref trap 'repeat-identical-behaviour)
  620.         (map uniquify-behaviour (slot-ref trap 'behaviour))
  621.         (slot-ref trap 'behaviour)))
  622.       '()))
  623.  
  624. ;;; Class: <procedure-trap>
  625. ;;;
  626. ;;; An installed instance of <procedure-trap> triggers on invocation
  627. ;;; of a specific procedure.
  628. (define-class <procedure-trap> (<trap>)
  629.   ;; "procedure" slot: the procedure to trap on.  This is implemented
  630.   ;; virtually, using the following weak vector slot, so as to avoid
  631.   ;; this trap preventing the GC of the target procedure.
  632.   (procedure #:init-keyword #:procedure
  633.          #:allocation #:virtual
  634.          #:slot-ref
  635.          (lambda (trap)
  636.            (vector-ref (slot-ref trap 'procedure-wv) 0))
  637.          #:slot-set!
  638.          (lambda (trap proc)
  639.            (if (slot-bound? trap 'procedure-wv)
  640.            (vector-set! (slot-ref trap 'procedure-wv) 0 proc)
  641.            (slot-set! trap 'procedure-wv (weak-vector proc)))))
  642.   (procedure-wv))
  643.  
  644. ;; Customization of the initialize method: set up to handle what
  645. ;; should happen when the procedure is GC'd.
  646. (define-method (initialize (trap <procedure-trap>) initargs)
  647.   (next-method)
  648.   (let* ((proc (slot-ref trap 'procedure))
  649.      (existing-traps (volatile-target-traps proc)))
  650.     ;; If this is the target's first trap, give the target procedure
  651.     ;; to the volatile-target-guardian, so we can find out if it
  652.     ;; becomes inaccessible.
  653.     (or existing-traps (volatile-target-guardian proc))
  654.     ;; Add this trap to the target procedure's list of traps.
  655.     (set! (volatile-target-traps proc)
  656.       (cons trap (or existing-traps '())))))
  657.  
  658. (define procedure-trace-count (make-object-property))
  659.  
  660. (define-method (install-trap (trap <procedure-trap>))
  661.   (next-method)
  662.   (let* ((proc (slot-ref trap 'procedure))
  663.          (trace-count (or (procedure-trace-count proc) 0)))
  664.     (set-procedure-property! proc 'trace #t)
  665.     (set! (procedure-trace-count proc) (+ trace-count 1)))
  666.   (install-trace-trap trap))
  667.  
  668. (define-method (uninstall-trap (trap <procedure-trap>))
  669.   (next-method)
  670.   (let* ((proc (slot-ref trap 'procedure))
  671.          (trace-count (or (procedure-trace-count proc) 0)))
  672.     (if (= trace-count 1)
  673.         (set-procedure-property! proc 'trace #f))
  674.     (set! (procedure-trace-count proc) (- trace-count 1)))
  675.   (uninstall-trace-trap trap))
  676.  
  677. (define-method (trap-runnable? (trap <procedure-trap>)
  678.                    (trap-context <trap-context>))
  679.   (eq? (slot-ref trap 'procedure)
  680.        (frame-procedure (tc:frame trap-context))))
  681.  
  682. ;;; Class: <exit-trap>
  683. ;;;
  684. ;;; An installed instance of <exit-trap> triggers on stack frame exit
  685. ;;; past a specified stack depth.
  686. (define-class <exit-trap> (<trap>)
  687.   ;; "depth" slot: the reference depth for the trap.
  688.   (depth #:init-keyword #:depth))
  689.  
  690. (define-method (install-trap (trap <exit-trap>))
  691.   (next-method)
  692.   (install-exit-frame-trap trap))
  693.  
  694. (define-method (uninstall-trap (trap <exit-trap>))
  695.   (next-method)
  696.   (uninstall-exit-frame-trap trap))
  697.  
  698. (define-method (trap-runnable? (trap <exit-trap>)
  699.                    (trap-context <trap-context>))
  700.   (<= (tc:exit-depth trap-context)
  701.       (slot-ref trap 'depth)))
  702.  
  703. ;;; Class: <entry-trap>
  704. ;;;
  705. ;;; An installed instance of <entry-trap> triggers on any frame entry.
  706. (define-class <entry-trap> (<trap>))
  707.  
  708. (define-method (install-trap (trap <entry-trap>))
  709.   (next-method)
  710.   (install-enter-frame-trap trap))
  711.  
  712. (define-method (uninstall-trap (trap <entry-trap>))
  713.   (next-method)
  714.   (uninstall-enter-frame-trap trap))
  715.  
  716. (define-method (trap-runnable? (trap <entry-trap>)
  717.                    (trap-context <trap-context>))
  718.   #t)
  719.  
  720. ;;; Class: <apply-trap>
  721. ;;;
  722. ;;; An installed instance of <apply-trap> triggers on any procedure
  723. ;;; application.
  724. (define-class <apply-trap> (<trap>))
  725.  
  726. (define-method (install-trap (trap <apply-trap>))
  727.   (next-method)
  728.   (install-apply-frame-trap trap))
  729.  
  730. (define-method (uninstall-trap (trap <apply-trap>))
  731.   (next-method)
  732.   (uninstall-apply-frame-trap trap))
  733.  
  734. (define-method (trap-runnable? (trap <apply-trap>)
  735.                    (trap-context <trap-context>))
  736.   #t)
  737.  
  738. ;;; Class: <step-trap>
  739. ;;;
  740. ;;; An installed instance of <step-trap> triggers on the next frame
  741. ;;; entry, exit or application, optionally with source location inside
  742. ;;; a specified file.
  743. (define-class <step-trap> (<exit-trap>)
  744.   ;; "file-name" slot: if non-#f, indicates that this trap should
  745.   ;; trigger only for steps in source code from the specified file.
  746.   (file-name #:init-value #f #:init-keyword #:file-name)
  747.   ;; "exit-depth" slot: when non-#f, indicates that the next step may
  748.   ;; be a frame exit past this depth; otherwise, indicates that the
  749.   ;; next step must be an application or a frame entry.
  750.   (exit-depth #:init-value #f #:init-keyword #:exit-depth))
  751.  
  752. (define-method (initialize (trap <step-trap>) initargs)
  753.   (next-method)
  754.   (slot-set! trap 'depth (slot-ref trap 'exit-depth)))
  755.  
  756. (define-method (install-trap (trap <step-trap>))
  757.   (next-method)
  758.   (install-enter-frame-trap trap)
  759.   (install-apply-frame-trap trap))
  760.  
  761. (define-method (uninstall-trap (trap <step-trap>))
  762.   (next-method)
  763.   (uninstall-enter-frame-trap trap)
  764.   (uninstall-apply-frame-trap trap))
  765.  
  766. (define-method (trap-runnable? (trap <step-trap>)
  767.                    (trap-context <trap-context>))
  768.   (if (eq? (tc:type trap-context) #:return)
  769.       ;; We're in the context of an exit-frame trap.  Trap should only
  770.       ;; be run if exit-depth is set and this exit-frame has returned
  771.       ;; past the set depth.
  772.       (and (slot-ref trap 'exit-depth)
  773.        (next-method)
  774.        ;; OK to run the trap here, but we should first reset the
  775.        ;; exit-depth slot to indicate that the step after this one
  776.        ;; must be an application or frame entry.
  777.        (begin
  778.          (slot-set! trap 'exit-depth #f)
  779.          #t))
  780.       ;; We're in the context of an application or frame entry trap.
  781.       ;; Check whether trap is limited to a specified file.
  782.       (let ((file-name (slot-ref trap 'file-name)))
  783.     (and (or (not file-name)
  784.          (equal? (frame-file-name (tc:frame trap-context)) file-name))
  785.          ;; Trap should run here, but we should also set exit-depth to
  786.          ;; the current stack length, so that - if we don't stop at any
  787.          ;; other steps first - the next step shows the return value of
  788.          ;; the current application or evaluation.
  789.          (begin
  790.            (slot-set! trap 'exit-depth (tc:depth trap-context))
  791.            (slot-set! trap 'depth (tc:depth trap-context))
  792.            #t)))))
  793.  
  794. (define (frame->source-position frame)
  795.   (let ((source (if (frame-procedure? frame)
  796.             (or (frame-source frame)
  797.             (let ((proc (frame-procedure frame)))
  798.               (and proc
  799.                    (procedure? proc)
  800.                    (procedure-source proc))))
  801.             (frame-source frame))))
  802.     (and source
  803.      (string? (source-property source 'filename))
  804.      (list (source-property source 'filename)
  805.            (source-property source 'line)
  806.            (source-property source 'column)))))
  807.  
  808. (define (frame-file-name frame)
  809.   (cond ((frame->source-position frame) => car)
  810.     (else #f)))
  811.  
  812. ;;; Class: <source-trap>
  813. ;;;
  814. ;;; An installed instance of <source-trap> triggers upon evaluation of
  815. ;;; a specified source expression.
  816. (define-class <source-trap> (<trap>)
  817.   ;; "expression" slot: the expression to trap on.  This is
  818.   ;; implemented virtually, using the following weak vector slot, so
  819.   ;; as to avoid this trap preventing the GC of the target source
  820.   ;; code.
  821.   (expression #:init-keyword #:expression
  822.           #:allocation #:virtual
  823.           #:slot-ref
  824.           (lambda (trap)
  825.         (vector-ref (slot-ref trap 'expression-wv) 0))
  826.           #:slot-set!
  827.           (lambda (trap expr)
  828.         (if (slot-bound? trap 'expression-wv)
  829.             (vector-set! (slot-ref trap 'expression-wv) 0 expr)
  830.             (slot-set! trap 'expression-wv (weak-vector expr)))))
  831.   (expression-wv)
  832.   ;; source property slots - for internal use only
  833.   (filename)
  834.   (line)
  835.   (column))
  836.  
  837. ;; Customization of the initialize method: get and save the
  838. ;; expression's source properties, or signal an error if it doesn't
  839. ;; have the necessary properties.
  840. (define-method (initialize (trap <source-trap>) initargs)
  841.   (next-method)
  842.   (let* ((expr (slot-ref trap 'expression))
  843.      (filename (source-property expr 'filename))
  844.          (line (source-property expr 'line))
  845.          (column (source-property expr 'column))
  846.      (existing-traps (volatile-target-traps expr)))
  847.     (or (and filename line column)
  848.         (error "Specified source does not have the necessary properties"
  849.                filename line column))
  850.     (slot-set! trap 'filename filename)
  851.     (slot-set! trap 'line line)
  852.     (slot-set! trap 'column column)
  853.     ;; If this is the target's first trap, give the target expression
  854.     ;; to the volatile-target-guardian, so we can find out if it
  855.     ;; becomes inaccessible.
  856.     (or existing-traps (volatile-target-guardian expr))
  857.     ;; Add this trap to the target expression's list of traps.
  858.     (set! (volatile-target-traps expr)
  859.       (cons trap (or existing-traps '())))))
  860.  
  861. ;; Just in case more than one trap is installed on the same source
  862. ;; expression ... so that we can still get the setting and resetting
  863. ;; of the 'breakpoint source property correct.
  864. (define source-breakpoint-count (make-object-property))
  865.  
  866. (define-method (install-trap (trap <source-trap>))
  867.   (next-method)
  868.   (let* ((expr (slot-ref trap 'expression))
  869.          (breakpoint-count (or (source-breakpoint-count expr) 0)))
  870.     (set-source-property! expr 'breakpoint #t)
  871.     (set! (source-breakpoint-count expr) (+ breakpoint-count 1)))
  872.   (install-breakpoint-trap trap))
  873.  
  874. (define-method (uninstall-trap (trap <source-trap>))
  875.   (next-method)
  876.   (let* ((expr (slot-ref trap 'expression))
  877.          (breakpoint-count (or (source-breakpoint-count expr) 0)))
  878.     (if (= breakpoint-count 1)
  879.         (set-source-property! expr 'breakpoint #f))
  880.     (set! (source-breakpoint-count expr) (- breakpoint-count 1)))
  881.   (uninstall-breakpoint-trap trap))
  882.  
  883. (define-method (trap-runnable? (trap <source-trap>)
  884.                    (trap-context <trap-context>))
  885.   (or (eq? (slot-ref trap 'expression)
  886.            (tc:expression trap-context))
  887.       (let ((trap-location (frame->source-position (tc:frame trap-context))))
  888.         (and trap-location
  889.              (string=? (car trap-location) (slot-ref trap 'filename))
  890.              (= (cadr trap-location) (slot-ref trap 'line))
  891.              (= (caddr trap-location) (slot-ref trap 'column))))))
  892.  
  893. ;; (trap-here EXPRESSION . OPTIONS)
  894. (define trap-here
  895.   (procedure->memoizing-macro
  896.    (lambda (expr env)
  897.      (let ((trap (apply make
  898.                         <source-trap>
  899.                         #:expression expr
  900.                         (local-eval `(list ,@(cddr expr))
  901.                                     env))))
  902.        (install-trap trap)
  903.        (set-car! expr 'begin)
  904.        (set-cdr! (cdr expr) '())
  905.        expr))))
  906.  
  907. ;;; Class: <location-trap>
  908. ;;;
  909. ;;; An installed instance of <location-trap> triggers on entry to a
  910. ;;; frame with a more-or-less precisely specified source location.
  911. (define-class <location-trap> (<trap>)
  912.   ;; "file-regexp" slot: regexp matching the name(s) of the file(s) to
  913.   ;; trap in.
  914.   (file-regexp #:init-keyword #:file-regexp)
  915.   ;; "line" and "column" slots: position to trap at (0-based).
  916.   (line #:init-value #f #:init-keyword #:line)
  917.   (column #:init-value #f #:init-keyword #:column)
  918.   ;; "compiled-regexp" slot - self explanatory, internal use only
  919.   (compiled-regexp))
  920.  
  921. (define-method (initialize (trap <location-trap>) initargs)
  922.   (next-method)
  923.   (slot-set! trap 'compiled-regexp
  924.              (make-regexp (slot-ref trap 'file-regexp))))
  925.  
  926. (define-method (install-trap (trap <location-trap>))
  927.   (next-method)
  928.   (install-enter-frame-trap trap))
  929.  
  930. (define-method (uninstall-trap (trap <location-trap>))
  931.   (next-method)
  932.   (uninstall-enter-frame-trap trap))
  933.  
  934. (define-method (trap-runnable? (trap <location-trap>)
  935.                    (trap-context <trap-context>))
  936.   (and-let* ((trap-location (frame->source-position (tc:frame trap-context)))
  937.          (tcline (cadr trap-location))
  938.          (tccolumn (caddr trap-location)))
  939.     (and (= tcline (slot-ref trap 'line))
  940.      (= tccolumn (slot-ref trap 'column))
  941.          (regexp-exec (slot-ref trap 'compiled-regexp)
  942.               (car trap-location) 0))))
  943.  
  944. ;;; {Misc Trap Utilities}
  945.  
  946. (define (get-trap number)
  947.   (hash-ref all-traps number))
  948.  
  949. (define (list-traps)
  950.   (for-each describe
  951.         (map cdr (sort (hash-fold acons '() all-traps)
  952.                (lambda (x y) (< (car x) (car y)))))))
  953.  
  954. ;;; {Volatile Traps}
  955. ;;;
  956. ;;; Some traps are associated with Scheme objects that are likely to
  957. ;;; be GC'd, such as procedures and read expressions.  When those
  958. ;;; objects are GC'd, we want to allow their traps to evaporate as
  959. ;;; well, or at least not to prevent them from doing so because they
  960. ;;; are (now pointlessly) included on the various installed trap
  961. ;;; lists.
  962.  
  963. ;; An object property that maps each volatile target to the list of
  964. ;; traps that are installed on it.
  965. (define volatile-target-traps (make-object-property))
  966.  
  967. ;; A guardian that tells us when a volatile target is no longer
  968. ;; accessible.
  969. (define volatile-target-guardian (make-guardian))
  970.  
  971. ;; An after GC hook that checks for newly inaccessible targets.
  972. (add-hook! after-gc-hook
  973.        (lambda ()
  974.          (trc 'after-gc-hook)
  975.          (let loop ((target (volatile-target-guardian)))
  976.            (if target
  977.                    ;; We have a target which is now inaccessible.  Get
  978.                    ;; the list of traps installed on it.
  979.            (begin
  980.              (trc 'after-gc-hook "got target")
  981.              ;; Uninstall all the traps that are installed on
  982.              ;; this target.
  983.              (for-each (lambda (trap)
  984.                  (trc 'after-gc-hook "got trap")
  985.                  ;; If the trap is still installed,
  986.                  ;; uninstall it.
  987.                  (if (slot-ref trap 'installed)
  988.                      (uninstall-trap trap))
  989.                  ;; If the trap has an observer, tell
  990.                  ;; it that the target has gone.
  991.                  (cond ((slot-ref trap 'observer)
  992.                     =>
  993.                     (lambda (proc)
  994.                       (trc 'after-gc-hook "call obs")
  995.                       (proc 'target-gone)))))
  996.                    (or (volatile-target-traps target) '()))
  997.                      ;; Check for any more inaccessible targets.
  998.              (loop (volatile-target-guardian)))))))
  999.  
  1000. (define (without-traps thunk)
  1001.   (with-traps (lambda ()
  1002.         (trap-disable 'traps)
  1003.         (thunk))))
  1004.  
  1005. (define guile-trap-features
  1006.   ;; Helper procedure, to test whether a specific possible Guile
  1007.   ;; feature is supported.
  1008.   (let ((supported?
  1009.          (lambda (test-feature)
  1010.            (case test-feature
  1011.              ((tweaking)
  1012.               ;; Tweaking is supported if the description of the cheap
  1013.               ;; traps option includes the word "obsolete", or if the
  1014.               ;; option isn't there any more.
  1015.               (and (string>=? (version) "1.7")
  1016.                    (let ((cheap-opt-desc
  1017.                           (assq 'cheap (debug-options-interface 'help))))
  1018.                      (or (not cheap-opt-desc)
  1019.                          (string-match "obsolete" (caddr cheap-opt-desc))))))
  1020.              (else
  1021.               (error "Unexpected feature name:" test-feature))))))
  1022.     ;; Compile the list of actually supported features from all
  1023.     ;; possible features.
  1024.     (let loop ((possible-features '(tweaking))
  1025.                (actual-features '()))
  1026.       (if (null? possible-features)
  1027.           (reverse! actual-features)
  1028.           (let ((test-feature (car possible-features)))
  1029.             (loop (cdr possible-features)
  1030.                   (if (supported? test-feature)
  1031.                       (cons test-feature actual-features)
  1032.                       actual-features)))))))
  1033.  
  1034. ;; Make sure that traps are enabled.
  1035. (trap-enable 'traps)
  1036.  
  1037. ;;; (ice-9 debugging traps) ends here.
  1038.