Finding the type of exception

When I'm writing around code that throws exceptions. Particularly when I'm going to wrap a side-effect in with-handlers I find myself often resorting to writing rackunit checks to find the exact type of exception being thrown. For example:

(define (valid-json? data)
  (with-handlers ([exn:fail:read? (lambda (e) #f)])
    (with-input-from-string data (lambda () (read-json) #t))))
(module+ test
(test-case "it does not throw an exception for invalid json"
      (check-not-exn (lambda () (valid-json? "no JSON here")))))

This seems like a very haphazard and some cases difficult to realise strategy for working out what type of exception is being thrown.

Does anyone have superior techniques for understanding what exceptions could be thrown by code?

1 Like

Would this work for you?

#lang racket

(define (find-exn f)
  (with-handlers ([exn:fail? println]
                  ; you can change exn:fail? to (λ (e) #t) too,
                  ; but that might be too extreme
                  )
    (f)))

(find-exn (λ () (/ 1 0))) ;=> (exn:fail:contract:divide-by-zero "/: division by zero" #<continuation-mark-set>)
(find-exn (λ () (first '()))) ;=> (exn:fail:contract "first: contract violation\n  expected: (and/c list? (not/c empty?))\n  given: '()" #<continuation-mark-set>)

2 Likes

While @sorawee 's reply answers how to find more information about the exception from a specific invocation, I'm interested in the "could", that @robertpostill used. :slight_smile:

Is there are way to find out what could be raised? So far the only way I know is to thoroughly read the documentation (and maybe miss some exceptions that may be raised by a function that is called by the function I want to call :wink: ) So I wonder if there's a way to introspect code for which exceptions it may raise (directly or indirectly).

2 Likes

Exceptions are also raised for things which are ultimately program defects. For example, a division by zero will raise the exn:fail:contract:divide-by-zero:

(with-handlers ((exn:fail:contract:divide-by-zero? displayln)) 
  (/ 1 0))

Calling a function with the wrong number of arguments, trying to assign an undefined variable, reading outside the range of a vector, and several others situations will also raise exceptions. Any program which has defects can potentially raise exceptions.

In addition to this, user breaks (hitting Control C) will raise an exn:break exception at any time during execution, and this is outside the control of the developer.

So, I think that you can safely assume that any code can raise an exception from the exn hierarchy.

Alex.

You can disable breaks for a thread can disable breaks by calling (break-enabled #f) or using (parameterize-break #f <body>). Breaks are also automatically disabled in certain places, such as exception handlers (see with-handlers).

Of course, it's usually not a good idea to disable breaks for a long time (in the main thread, at least), so the point about exn:break still stands.

1 Like

Was this intended to say that "any code can raise any exception from the exn hierarchy." ? I'm guessing that's what you meant, and I agree.

Of course, you could add a system like Java's, which—for a subset of exceptions—adds a static tracking mechanism to allow you to know exactly which exceptions could occur. That sounds to me like the kind of modification that would be delightfully impossible to bolt on to a dynamic language.

3 Likes

Here is a naive hack static exception checker. To be remotely usable, it would still need a way of communicating the exceptions raised by a function and a library of exception signatures for existing standard library functions. Also, it would need to know about the exception hierarchy.

1 Like