unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
From: Andrew Whatson <whatson@gmail.com>
To: guile-devel@gnu.org
Cc: Andrew Whatson <whatson@gmail.com>
Subject: [DRAFT] Improve reporting of exception locations
Date: Wed, 12 Oct 2022 16:42:40 +1000	[thread overview]
Message-ID: <20221012064239.30295-1-whatson@gmail.com> (raw)
In-Reply-To: <CAPE069f6e1n3+HTLfbNjy7P7FoqFZiG1n5s9QfMu+cFywjgMBQ@mail.gmail.com>

TODO: use exception-stack in other error handlers
TODO: print-exception variant for exception objects
TODO: test cases for other kinds of errors

* module/ice-9/exceptions.scm (&stack): New exception type.
(convert-guile-exception): Capture the stack when creating an exception.
* module/system/repl/error-handling.scm (call-with-error-handling): Use
the exception stack if available.
* test-suite/Makefile.am (SCM_TESTS): Add sample code.
* test-suite/tests/exceptions.test ("exception location"): New tests.
* test-suite/tests/exceptions/error.scm:
* test-suite/tests/exceptions/error-with-throw-handler-1.scm:
* test-suite/tests/exceptions/error-with-throw-handler-2.scm:
* test-suite/tests/exceptions/error-with-throw-handler-3.scm:
Sample code for exception location tests.
---
 module/ice-9/exceptions.scm                   |   8 +-
 module/system/repl/error-handling.scm         | 153 ++++++++++--------
 test-suite/Makefile.am                        |   5 +
 test-suite/tests/exceptions.test              |  48 +++++-
 .../exceptions/error-with-throw-handler-1.scm |   6 +
 .../exceptions/error-with-throw-handler-2.scm |  11 ++
 .../exceptions/error-with-throw-handler-3.scm |  16 ++
 test-suite/tests/exceptions/error.scm         |   1 +
 8 files changed, 183 insertions(+), 65 deletions(-)
 create mode 100644 test-suite/tests/exceptions/error-with-throw-handler-1.scm
 create mode 100644 test-suite/tests/exceptions/error-with-throw-handler-2.scm
 create mode 100644 test-suite/tests/exceptions/error-with-throw-handler-3.scm
 create mode 100644 test-suite/tests/exceptions/error.scm

diff --git a/module/ice-9/exceptions.scm b/module/ice-9/exceptions.scm
index 143e7aa3e..207c79e2a 100644
--- a/module/ice-9/exceptions.scm
+++ b/module/ice-9/exceptions.scm
@@ -48,6 +48,7 @@
             make-exception-with-message
             exception-with-message?
             exception-message
+            exception-stack
 
             &warning
             make-warning
@@ -154,6 +155,10 @@
   make-exception-with-origin exception-with-origin?
   (origin exception-origin))
 
+(define-exception-type &stack &exception
+  make-exception-with-stack exception-with-stack?
+  (stack exception-stack))
+
 (define-exception-type-procedures &non-continuable &programming-error
   make-non-continuable-error
   non-continuable-error?)
@@ -198,7 +203,8 @@
   (let ((converter (assv-ref guile-exception-converters key)))
     (make-exception (or (and converter (converter key args))
                         (default-guile-exception-converter key args))
-                    (make-exception-with-kind-and-args key args))))
+                    (make-exception-with-kind-and-args key args)
+                    (make-exception-with-stack (make-stack #t 4)))))
 
 (define (raise-continuable obj)
   (raise-exception obj #:continuable? #t))
diff --git a/module/system/repl/error-handling.scm b/module/system/repl/error-handling.scm
index 8d5a8a5f0..e2a2458c0 100644
--- a/module/system/repl/error-handling.scm
+++ b/module/system/repl/error-handling.scm
@@ -23,6 +23,7 @@
   #:use-module (system base pmatch)
   #:use-module (system vm trap-state)
   #:use-module (system repl debug)
+  #:use-module (ice-9 exceptions)
   #:use-module (ice-9 format)
   #:export (call-with-error-handling
             with-error-handling))
@@ -101,83 +102,109 @@
          (run-hook after-error-hook)
          (force-output err))))
 
-    (catch #t
-      (lambda () 
-        (with-default-trap-handler le-trap-handler
-          (lambda () (%start-stack #t thunk))))
-
+    (define post-error-handler
       (case post-error
         ((report)
-         (lambda (key . args)
-           (if (memq key pass-keys)
-               (apply throw key args)
-               (begin
-                 (report-error key args)
-                 (if #f #f)))))
+         (lambda (exn)
+           (let ((key (exception-kind exn))
+                 (args (exception-args exn)))
+             (when (memq key pass-keys)
+               (raise-exception exn))
+             (report-error key args)
+             (if #f #f))))
         ((catch)
-         (lambda (key . args)
-           (when (memq key pass-keys)
-             (apply throw key args))
-           (when (memq key report-keys)
-             (report-error key args))
-           (if #f #f)))
+         (lambda (exn)
+           (let ((key (exception-kind exn))
+                 (args (exception-args exn)))
+             (when (memq key pass-keys)
+               (raise-exception exn))
+             (when (memq key report-keys)
+               (report-error key args))
+             (if #f #f))))
         (else
          (if (procedure? post-error)
-             (lambda (k . args)
-               (apply (if (memq k pass-keys) throw post-error) k args))
-             (error "Unknown post-error strategy" post-error))))
-
+             (lambda (exn)
+               (let ((key (exception-kind exn))
+                     (args (exception-args exn)))
+                 (when (memq key pass-keys)
+                   (raise-exception exn))
+                 (apply post-error key args)))
+             (error "Unknown post-error strategy" post-error)))))
+
+    (define on-error-handler
       (case on-error
         ((debug)
-         (lambda (key . args)
-           (if (not (memq key pass-keys))
-               (let* ((tag (and (pair? (fluid-ref %stacks))
-                                (cdr (fluid-ref %stacks))))
-                      (stack (narrow-stack->vector
-                              (make-stack #t)
-                              ;; Cut three frames from the top of the stack:
-                              ;; make-stack, this one, and the throw handler.
-                              3
-                              ;; Narrow the end of the stack to the most recent
-                              ;; start-stack.
-                              tag
-                              ;; And one more frame, because %start-stack invoking
-                              ;; the start-stack thunk has its own frame too.
-                              0 (and tag 1)))
-                      (error-msg (error-string stack key args))
-                      (debug (make-debug stack 0 error-msg)))
-                 (with-saved-ports
-                  (lambda ()
-                    (format #t "~a~%" error-msg)
-                    (format #t "Entering a new prompt.  ")
-                    (format #t "Type `,bt' for a backtrace or `,q' to continue.\n")
-                    ((@ (system repl repl) start-repl) #:debug debug)))))))
+         (lambda (exn)
+           (let ((key (exception-kind exn))
+                 (args (exception-args exn)))
+             (if (not (memq key pass-keys))
+                 (let* ((tag (and (pair? (fluid-ref %stacks))
+                                  (cdr (fluid-ref %stacks))))
+                        (stack (or (exception-stack exn)
+                                   ;; Cut three frames from the top of the stack:
+                                   ;; make-stack, this one, and the throw handler.
+                                   (make-stack #t 3)))
+                        (stack (narrow-stack->vector
+                                stack
+                                ;; Narrow the end of the stack to the most recent
+                                ;; start-stack.
+                                0 tag
+                                ;; And one more frame, because %start-stack invoking
+                                ;; the start-stack thunk has its own frame too.
+                                0 (and tag 1)))
+                        (error-msg (error-string stack key args))
+                        (debug (make-debug stack 0 error-msg)))
+                   (with-saved-ports
+                    (lambda ()
+                      (format #t "~a~%" error-msg)
+                      (format #t "Entering a new prompt.  ")
+                      (format #t "Type `,bt' for a backtrace or `,q' to continue.\n")
+                      ((@ (system repl repl) start-repl) #:debug debug))))))))
         ((report)
-         (lambda (key . args)
-           (unless (memq key pass-keys)
-             (report-error key args))
-           (if #f #f)))
+         (lambda (exn)
+           (let ((key (exception-kind exn))
+                 (args (exception-args exn)))
+             (unless (memq key pass-keys)
+               (report-error key args))
+             (if #f #f))))
         ((backtrace)
-         (lambda (key . args)
-           (if (not (memq key pass-keys))
-               (let* ((tag (and (pair? (fluid-ref %stacks))
-                                (cdr (fluid-ref %stacks))))
-                      (frames (narrow-stack->vector
-                               (make-stack #t)
-                               ;; Narrow as above, for the debugging case.
-                               3 tag 0 (and tag 1))))
-                 (with-saved-ports (lambda () (print-frames frames)))
-                 (report-error key args)
-                 (if #f #f)))))
+         (lambda (exn)
+           (let ((key (exception-kind exn))
+                 (args (exception-args exn)))
+             (if (not (memq key pass-keys))
+                 (let* ((tag (and (pair? (fluid-ref %stacks))
+                                  (cdr (fluid-ref %stacks))))
+                        (frames (narrow-stack->vector
+                                 (make-stack #t)
+                                 ;; Narrow as above, for the debugging case.
+                                 3 tag 0 (and tag 1))))
+                   (with-saved-ports (lambda () (print-frames frames)))
+                   (report-error key args)
+                   (if #f #f))))))
         ((pass)
-         (lambda (key . args)
+         (lambda (exn)
            ;; fall through to rethrow
            #t))
         (else
          (if (procedure? on-error)
-             (lambda (k . args)
-               (apply (if (memq k pass-keys) throw on-error) k args))
-             (error "Unknown on-error strategy" on-error)))))))
+             (lambda (exn)
+               (let ((key (exception-kind exn))
+                     (args (exception-args exn)))
+                 (when (memq k pass-keys)
+                   (raise-exception exn))
+                 (apply on-error key args)))
+             (error "Unknown on-error strategy" on-error)))))
+
+    (with-exception-handler post-error-handler
+      (lambda ()
+        (with-exception-handler
+            (lambda (exn)
+              (on-error-handler exn)
+              (raise-exception exn))
+          (lambda ()
+            (with-default-trap-handler le-trap-handler
+              (lambda () (%start-stack #t thunk))))))
+      #:unwind? #t)))
 
 (define-syntax-rule (with-error-handling form)
   (call-with-error-handling (lambda () form)))
diff --git a/test-suite/Makefile.am b/test-suite/Makefile.am
index 35f264195..fb44e2841 100644
--- a/test-suite/Makefile.am
+++ b/test-suite/Makefile.am
@@ -54,6 +54,11 @@ SCM_TESTS = tests/00-initial-env.test		\
 	    tests/eval.test			\
 	    tests/eval-string.test		\
 	    tests/exceptions.test		\
+	    tests/exceptions.test		\
+	    tests/exceptions/error.scm          \
+	    tests/exceptions/error-with-throw-handler-1.scm \
+	    tests/exceptions/error-with-throw-handler-2.scm \
+	    tests/exceptions/error-with-throw-handler-3.scm \
 	    tests/fdes-finalizers.test		\
 	    tests/filesys.test			\
 	    tests/fluids.test			\
diff --git a/test-suite/tests/exceptions.test b/test-suite/tests/exceptions.test
index 291e10e26..66b7cfbdb 100644
--- a/test-suite/tests/exceptions.test
+++ b/test-suite/tests/exceptions.test
@@ -17,7 +17,8 @@
 
 
 (define-module (test-suite exceptions)
-  #:use-module (test-suite lib))
+  #:use-module (test-suite lib)
+  #:use-module (ice-9 exceptions))
 
 (define-syntax-parameter push
   (lambda (stx)
@@ -392,3 +393,48 @@
       (let* ((thunk1 (catch* 'foo (lambda () (throw 'bar))))
              (thunk2 (catch* 'bar (lambda () (thunk1)))))
         (thunk2))))
+
+(with-test-prefix "exception location"
+  (define (test-file filename)
+    (string-append
+     (dirname (current-filename)) "/" filename))
+
+  (define (try-load filename)
+    (with-exception-handler values
+      (lambda ()
+        (load filename)
+        #f) ;; success is failure
+      #:unwind? #t))
+
+  (define (check-location expected)
+    (lambda (exn)
+      (pk exn)
+      (let* ((key (exception-kind exn))
+             (args (exception-args exn))
+             (stack (exception-stack exn))
+             (frame (and stack (stack-ref stack 0)))
+             (message (call-with-output-string
+                        (lambda (port)
+                          (print-exception port frame key args)))))
+        ;; (when stack
+        ;;   (display-backtrace stack (current-warning-port)))
+        ;; (format (current-warning-port)
+        ;;         "\n;;; message: ~s\n;;; expected: ~s\n\n"
+        ;;         message expected)
+        (number? (string-contains message expected)))))
+
+  (pass-if "error"
+    (and=> (try-load (test-file "exceptions/error.scm"))
+           (check-location "exceptions/error.scm:1")))
+
+  (pass-if "error with one throw handler"
+    (and=> (try-load (test-file "exceptions/error-with-throw-handler-1.scm"))
+           (check-location "exceptions/error-with-throw-handler-1.scm:3")))
+
+  (pass-if "error with two throw handlers"
+    (and=> (try-load (test-file "exceptions/error-with-throw-handler-2.scm"))
+           (check-location "exceptions/error-with-throw-handler-2.scm:5")))
+
+  (pass-if "error with three throw handlers"
+    (and=> (try-load (test-file "exceptions/error-with-throw-handler-3.scm"))
+           (check-location "exceptions/error-with-throw-handler-3.scm:7"))))
diff --git a/test-suite/tests/exceptions/error-with-throw-handler-1.scm b/test-suite/tests/exceptions/error-with-throw-handler-1.scm
new file mode 100644
index 000000000..4acaa7256
--- /dev/null
+++ b/test-suite/tests/exceptions/error-with-throw-handler-1.scm
@@ -0,0 +1,6 @@
+(with-throw-handler 'foo
+  (lambda ()
+    (error "whoops!"))
+  (lambda ()
+    ;; unused
+    (values)))
diff --git a/test-suite/tests/exceptions/error-with-throw-handler-2.scm b/test-suite/tests/exceptions/error-with-throw-handler-2.scm
new file mode 100644
index 000000000..8dc04c081
--- /dev/null
+++ b/test-suite/tests/exceptions/error-with-throw-handler-2.scm
@@ -0,0 +1,11 @@
+(with-throw-handler 'bar
+  (lambda ()
+    (with-throw-handler 'foo
+      (lambda ()
+        (error "whoops!"))
+      (lambda ()
+        ;; unused
+        (values))))
+  (lambda ()
+    ;; unused
+    (values)))
diff --git a/test-suite/tests/exceptions/error-with-throw-handler-3.scm b/test-suite/tests/exceptions/error-with-throw-handler-3.scm
new file mode 100644
index 000000000..62c8b033b
--- /dev/null
+++ b/test-suite/tests/exceptions/error-with-throw-handler-3.scm
@@ -0,0 +1,16 @@
+(with-throw-handler 'baz
+  (lambda ()
+    (with-throw-handler 'bar
+      (lambda ()
+        (with-throw-handler 'foo
+          (lambda ()
+            (error "whoops!"))
+          (lambda ()
+            ;; unused
+            (values))))
+      (lambda ()
+        ;; unused
+        (values))))
+  (lambda ()
+    ;; unused
+    (values)))
diff --git a/test-suite/tests/exceptions/error.scm b/test-suite/tests/exceptions/error.scm
new file mode 100644
index 000000000..7ba405341
--- /dev/null
+++ b/test-suite/tests/exceptions/error.scm
@@ -0,0 +1 @@
+(error "whoops!")
-- 
2.38.0




  reply	other threads:[~2022-10-12  6:42 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-12  6:35 [DRAFT] Improve reporting of exception locations Andrew Whatson
2022-10-12  6:42 ` Andrew Whatson [this message]
2022-10-12 19:16 ` Maxime Devos

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/guile/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221012064239.30295-1-whatson@gmail.com \
    --to=whatson@gmail.com \
    --cc=guile-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).