;;; exceptions.ss ;;; Copyright 1984-2017 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. #| TODO: - teach default handler to: - squirrel away continuation for debug as &continuation simple condition - say something about calling debug (if &continuation is included) - teach reset to handle closing of ports, etc., in system error chain - wire into existing error-handling mechanisms, or visa versa - replace error calls as appropriate with violation calls, syntax-violation calls, etc. - fix: unbound variables show up as #{b *top*:b} (~:s in message is supposed to take care of this but format isn't being called) - mats for system violations and errors - deal with error? and warning? mats |# (begin (let () (define (warning-only? c) (and (warning? c) (not (serious-condition? c)))) (let () (define $display-condition (lambda (c op prefix? use-cache?) (module (print-source) (include "types.ss") (define (print-position op prefix src start?) (call-with-values (lambda () ((current-locate-source-object-source) src start? use-cache?)) (case-lambda [() (let ([sfd (source-sfd src)] [fp (if start? (source-bfp src) (source-efp src))]) (fprintf op "~a~a char ~a of ~a" prefix (if (eq? start? 'near) "near" "at") fp (source-file-descriptor-name sfd)))] [(path line char) (fprintf op "~a~a line ~a, char ~a of ~a" prefix (if (eq? start? 'near) "near" "at") line char path)]))) (define (print-source op prefix c) (cond [($src-condition? c) (let ([src ($src-condition-src c)]) (when (source? src) (print-position op prefix src ($src-condition-start c))))] [(source-condition? c) (let ([form (source-condition-form c)]) (parameterize ([print-level 3] [print-length 6]) (fprintf op "~a~s" prefix (syntax->datum form))) (let-values ([(src start?) ($syntax->src form)]) (when src (print-position op " " src start?))))] [(syntax-violation? c) (let ([form (syntax-violation-form c)] [subform (syntax-violation-subform c)]) (parameterize ([print-level 3] [print-length 6]) (if subform (fprintf op "~a~s in ~s" prefix (syntax->datum subform) (syntax->datum form)) (fprintf op "~a~s" prefix (syntax->datum form)))) (let-values ([(src start?) ($syntax->src subform)]) (if src (print-position op " " src start?) (let-values ([(src start?) ($syntax->src form)]) (when src (print-position op " " src start?))))))]))) (cond [(and (format-condition? c) (guard (ignore [#t #f]) ($report-string #f (and prefix? (if (warning-only? c) "warning" "exception")) (and (who-condition? c) (condition-who c)) (condition-message c) (condition-irritants c)))) => (lambda (s) (display s op) (print-source op " " c))] [(message-condition? c) (let ([irritants (if (irritants-condition? c) (condition-irritants c) '())]) (case (and (list? irritants) (length irritants)) [(0) ($report-string op (and prefix? (if (warning-only? c) "warning" "exception")) (and (who-condition? c) (condition-who c)) "~a" (list (condition-message c)))] [(1) ($report-string op (and prefix? (if (warning-only? c) "warning" "exception")) (and (who-condition? c) (condition-who c)) "~a with irritant ~s" (list (condition-message c) (car irritants)))] [else ($report-string op (and prefix? (if (warning-only? c) "warning" "exception")) (and (who-condition? c) (condition-who c)) "~a with irritants ~s" (list (condition-message c) irritants))])) (print-source op " " c)] [else (fprintf op "Exception occurred") (cond [(condition? c) (print-source op " " c) (let ([x* (simple-conditions c)]) (cond [(null? x*) (fprintf op " with empty condition\n")] [else (fprintf op " with condition components:") (for-each (lambda (x i) (let ([rtd (#3%record-rtd x)]) (define (print-field i) (if (csv7:record-field-accessible? rtd i) (parameterize ([print-level 3] [print-length 6]) (fprintf op ": ~s" ((csv7:record-field-accessor rtd i) x))) (fprintf op ": (inaccessible)"))) (fprintf op "\n~3d. ~a" i (csv7:record-type-name (#3%record-rtd x))) (if (record-type-opaque? rtd) (fprintf op " (opaque)") (let ([name* (csv7:record-type-field-names rtd)]) (if (fx= (length name*) 1) (print-field 0) (for-each (lambda (name i) (fprintf op "\n ~s" name) (print-field i)) name* (iota (length name*)))))))) x* (iota (length x*)))]))] [else (parameterize ([print-level 3] [print-length 6]) (fprintf op " with non-condition value ~s" c))])]))) (set-who! display-condition (case-lambda [(c) ($display-condition c (current-output-port) #t #f)] [(c op) (unless (and (output-port? op) (textual-port? op)) ($oops who "~s is not a textual output port" op)) ($display-condition c op #t #f)])) (set! $make-source-oops (lambda (who msg expr) #`(assertion-violation '#,who #,(call-with-string-output-port (lambda (p) ($display-condition (condition (make-syntax-violation expr #f) (make-message-condition msg)) p #f #t))))))) (set! default-exception-handler (lambda (c) (let ([cep (console-error-port)]) (with-exception-handler (lambda (c) (if (i/o-error? c) (begin (debug-condition c) (if (debug-on-exception) (debug)) (reset)) (raise-continuable c))) (lambda () ; only I/O to cep in handler-protected code---not (debug), not (reset). (fresh-line cep) (display-condition c cep) (newline cep) (unless (or (warning-only? c) (debug-on-exception) (= ($cafe) 0) (not (interactive?))) (display-string "Type (debug) to enter the debugger.\n" cep)) (flush-output-port cep)))) (unless (warning-only? c) (debug-condition c) (if (debug-on-exception) (debug)) (reset))))) (define debug-on-exception (make-parameter #f (lambda (x) (and x #t)))) (define base-exception-handler ($make-thread-parameter default-exception-handler (lambda (p) (unless (procedure? p) ($oops 'default-exception-handler "~s is not a procedure" p)) p))) (let () (define create-exception-stack (lambda (p) (let ([ls (list p)]) (set-cdr! ls ls) ls))) (define default-handler (lambda (x) ((base-exception-handler) x))) (define-threaded handler-stack (create-exception-stack default-handler)) (let () (define-record-type exception-state (nongenerative) (opaque #t) (sealed #t) (fields (immutable stack))) (set-who! create-exception-state (case-lambda [() (make-exception-state (create-exception-stack default-handler))] [(p) (unless (procedure? p) ($oops who "~s is not a procedure" p)) (make-exception-state (create-exception-stack p))])) (set-who! current-exception-state (case-lambda [() (make-exception-state handler-stack)] [(x) (unless (exception-state? x) ($oops who "~s is not an exception state" x)) (set! handler-stack (exception-state-stack x))]))) (set-who! with-exception-handler (lambda (handler thunk) (unless (procedure? handler) ($oops who "~s is not a procedure" handler)) (unless (procedure? thunk) ($oops who "~s is not a procedure" thunk)) (fluid-let ([handler-stack (cons handler handler-stack)]) (thunk)))) (set-who! raise (lambda (obj) (let ([handler (car handler-stack)]) (fluid-let ([handler-stack (cdr handler-stack)]) (handler obj) (raise (make-non-continuable-violation)))))) (set-who! raise-continuable (lambda (obj) (let ([handler (car handler-stack)]) (fluid-let ([handler-stack (cdr handler-stack)]) (handler obj))))) (set-who! $guard (lambda (supply-else? guards body) (if supply-else? ((call/cc (lambda (kouter) (let ([original-handler-stack handler-stack]) (with-exception-handler (lambda (arg) ((call/cc (lambda (kinner) (kouter (lambda () (guards arg (lambda () (kinner (lambda () (fluid-let ([handler-stack original-handler-stack]) (raise-continuable arg)))))))))))) (lambda () (call-with-values body (case-lambda [(x) (lambda () x)] [vals (lambda () (apply values vals))])))))))) ((call/cc (lambda (k) (with-exception-handler (lambda (arg) (k (lambda () (guards arg)))) (lambda () (call-with-values body (case-lambda [(x) (lambda () x)] [vals (lambda () (apply values vals))])))))))))) ) (define-syntax guard (syntax-rules (else) [(_ (var clause ... [else e1 e2 ...]) b1 b2 ...) (identifier? #'var) ($guard #f (lambda (var) (cond clause ... [else e1 e2 ...])) (lambda () b1 b2 ...))] [(_ (var clause1 clause2 ...) b1 b2 ...) (identifier? #'var) ($guard #t (lambda (var p) (cond clause1 clause2 ... [else (p)])) (lambda () b1 b2 ...))])) (let () ; redefine here to get local predicate (define-record-type (&condition $make-simple-condition $simple-condition?) (nongenerative #{&condition oyb459ue1fphfx4-a})) (define-record-type (compound-condition make-compound-condition compound-condition?) (nongenerative) (sealed #t) (fields (immutable components))) (define (check-&condition-subtype! who rtd) (unless (record-type-descriptor? rtd) ($oops who "~s is not a record type descriptor" rtd)) (unless (let f ([rtd rtd]) (or (eq? rtd (type-descriptor &condition)) (let ([rtd (record-type-parent rtd)]) (and rtd (f rtd))))) ($oops who "~s does not describe a subtype of &condition" rtd))) (record-writer (type-descriptor &condition) (lambda (x p wr) (fprintf p "#" (csv7:record-type-name (#3%record-rtd x))))) (record-writer (type-descriptor compound-condition) (lambda (x p wr) (fprintf p "#"))) (set-who! $compound-condition? compound-condition?) (set-who! $compound-condition-components compound-condition-components) (set-who! condition (case-lambda [(x) (unless (or ($simple-condition? x) (compound-condition? x)) ($oops who "~s is not a condition" x)) x] [x* (let ([ls (fold-right (lambda (x ls) (cond [($simple-condition? x) (cons x ls)] [(compound-condition? x) (append (compound-condition-components x) ls)] [else ($oops who "~s is not a condition" x)])) '() x*)]) (if (fx= (length ls) 1) (car ls) (make-compound-condition ls)))])) (set-who! simple-conditions (lambda (x) (cond [($simple-condition? x) (list x)] [(compound-condition? x) (compound-condition-components x)] [else ($oops who "~s is not a condition" x)]))) (set! condition? (lambda (x) (or ($simple-condition? x) (compound-condition? x)))) (set-who! condition-predicate (lambda (rtd) (check-&condition-subtype! who rtd) (let ([p? (lambda (x) (record? x rtd))]) (lambda (x) (or (p? x) (and (compound-condition? x) (ormap p? (compound-condition-components x)))))))) (set-who! condition-accessor (lambda (rtd proc) (define accessor-error (lambda (x rtd) ($oops 'generated-condition-accessor "~s is not a condition of the type represented by ~s" x rtd))) (check-&condition-subtype! who rtd) (rec generated-condition-accessor (lambda (x) (cond [(record? x rtd) (proc x)] [(compound-condition? x) (let f ([ls (compound-condition-components x)]) (if (null? ls) (accessor-error x rtd) (let ([x (car ls)]) (if (record? x rtd) (proc x) (f (cdr ls))))))] [else (accessor-error x rtd)])))))) (define-syntax define-condition-type (lambda (x) (syntax-case x () [(_ type-name super-type constructor predicate? (field-name accessor) ...) (with-syntax ([($accessor ...) (generate-temporaries #'(accessor ...))] [msg (format "~~s is not a condition of type ~a" (datum type-name))]) #'(begin (define-record-type (type-name constructor $predicate?) (nongenerative) (parent super-type) (fields (immutable field-name $accessor) ...)) (define predicate? (lambda (x) (or ($predicate? x) (and ($compound-condition? x) (ormap $predicate? ($compound-condition-components x)))))) (define accessor (lambda (x) (define accessor-error (lambda (x) ($oops 'accessor msg x))) (cond [($predicate? x) ($accessor x)] [($compound-condition? x) (let f ([ls ($compound-condition-components x)]) (if (null? ls) (accessor-error x) (let ([x (car ls)]) (if ($predicate? x) ($accessor x) (f (cdr ls))))))] [else (accessor-error x)]))) ...))]))) (eval-when (compile) (define-syntax define-system-condition-type (lambda (x) (syntax-case x () [(_ type-name super-type uid constructor predicate? (field-name accessor) ...) (with-syntax ([($accessor ...) (generate-temporaries #'(accessor ...))] [msg (format "~~s is not a condition of type ~a" (datum type-name))]) #'(begin (define-record-type (type-name constructor $predicate?) (nongenerative uid) (parent super-type) (fields (immutable field-name $accessor) ...)) (define predicate? (lambda (x) (or ($predicate? x) (and ($compound-condition? x) (ormap $predicate? ($compound-condition-components x)))))) (define accessor (lambda (x) (define accessor-error (lambda (x) ($oops 'accessor msg x))) (cond [($predicate? x) ($accessor x)] [($compound-condition? x) (let f ([ls ($compound-condition-components x)]) (if (null? ls) (accessor-error x) (let ([x (car ls)]) (if ($predicate? x) ($accessor x) (f (cdr ls))))))] [else (accessor-error x)]))) ...))]))) ) ;;; standard condition types ;;; taking advantage of body-like semantics of begin to arrange for each ;;; condition type's compile-time information to be available for use in ;;; defining its child types, even though the system is compiled with ;;; (eval-syntax-expanders-when) not including compile. (begin (let-syntax ([a (syntax-rules () [(_ &condition) ; leave only &condition visible (define-record-type (&condition make-simple-condition simple-condition?) (nongenerative #{&condition oyb459ue1fphfx4-a}))])]) (a &condition)) (define-system-condition-type &message &condition #{&message bwptyckgidgnsihx-a} make-message-condition message-condition? (message condition-message)) (define-system-condition-type &warning &condition #{&warning bwtai41dgaww3fus-a} make-warning warning?) (define-system-condition-type &serious &condition #{&serious bwvzuvr26s58u3l9-a} make-serious-condition serious-condition?) (define-system-condition-type &error &serious #{&error bwyo6misxbfkmrdg-a} make-error error?) (define-system-condition-type &violation &serious #{&violation bw1eic9intowee4m-a} make-violation violation?) (define-system-condition-type &assertion &violation #{&assertion bw33t3z8ebx752vs-a} make-assertion-violation assertion-violation?) (define-system-condition-type &irritants &condition #{&irritants bw6s5uqx4t7jxqmy-a} make-irritants-condition irritants-condition? (irritants condition-irritants)) (define-system-condition-type &who &condition #{&who bw9ihlhnvcgvped6-a} make-who-condition who-condition? (who condition-who)) (define-system-condition-type &non-continuable &violation #{&non-continuable bxb7tb8dlup7g15e-a} make-non-continuable-violation non-continuable-violation?) (define-system-condition-type &implementation-restriction &violation #{&implementation-restriction bxew42y3cczi8pwl-a} make-implementation-restriction-violation implementation-restriction-violation?) (define-system-condition-type &lexical &violation #{&lexical bxhmgtps2u8u0dns-a} make-lexical-violation lexical-violation?) (define-system-condition-type &syntax &violation #{&syntax bxkbskgitdh6r1ey-a} make-syntax-violation syntax-violation? (form syntax-violation-form) (subform syntax-violation-subform)) (define-system-condition-type &undefined &violation #{&undefined bxm04a68jvrijo54-a} make-undefined-violation undefined-violation?) ;;; io conditions (define-system-condition-type &i/o &error #{&i/o bxpqf1xyad0ubcxc-a} make-i/o-error i/o-error?) (define-system-condition-type &i/o-read &i/o #{&i/o-read bxsfrson0v9520oj-a} make-i/o-read-error i/o-read-error?) (define-system-condition-type &i/o-write &i/o #{&i/o-write bxu43jfdrejhuofp-a} make-i/o-write-error i/o-write-error?) (define-system-condition-type &i/o-invalid-position &i/o #{&i/o-invalid-position bxxue953hwstmb6v-a} make-i/o-invalid-position-error i/o-invalid-position-error? (position i/o-error-position)) (define-system-condition-type &i/o-filename &i/o #{&i/o-filename bx0jq0ws8e15dzx4-a} make-i/o-filename-error i/o-filename-error? (filename i/o-error-filename)) (define-system-condition-type &i/o-file-protection &i/o-filename #{&i/o-file-protection bx282rniyxbg5npc-a} make-i/o-file-protection-error i/o-file-protection-error?) (define-system-condition-type &i/o-file-is-read-only &i/o-file-protection #{&i/o-file-is-read-only bx5yeid8pfksxbgj-a} make-i/o-file-is-read-only-error i/o-file-is-read-only-error?) (define-system-condition-type &i/o-file-already-exists &i/o-filename #{&i/o-file-already-exists bx8np84yfxt4oy7q-a} make-i/o-file-already-exists-error i/o-file-already-exists-error?) (define-system-condition-type &i/o-file-does-not-exist &i/o-filename #{&i/o-file-does-not-exist bybc1zvn6f3ggmyw-a} make-i/o-file-does-not-exist-error i/o-file-does-not-exist-error?) (define-system-condition-type &i/o-port &i/o #{&i/o-port byd2dqmdwycr8ap5-a} make-i/o-port-error i/o-port-error? (pobj i/o-error-port)) (define-system-condition-type &i/o-decoding &i/o-port #{&i/o-decoding bygrphc3ngl3zyhc-a} make-i/o-decoding-error i/o-decoding-error?) (define-system-condition-type &i/o-encoding &i/o-port #{&i/o-encoding byjg073tdyvfrl8i-a} make-i/o-encoding-error i/o-encoding-error? (cobj i/o-encoding-error-char)) ;;; arithmetic conditions (define-system-condition-type &no-infinities &implementation-restriction #{&no-infinities byl6cyui4g4ri9zq-a} make-no-infinities-violation no-infinities-violation?) (define-system-condition-type &no-nans &implementation-restriction #{&no-nans byovopk8uzd3axqx-a} make-no-nans-violation no-nans-violation?) ;;; Chez Scheme conditions (define-system-condition-type &source &condition #{&source byrk0gbylhne2lh4-a} make-source-condition source-condition? (form source-condition-form)) (define-system-condition-type $&src &condition #{$&src byul0m8re6e47nnb-a} $make-src-condition $src-condition? (src $src-condition-src) (start $src-condition-start)) (define-system-condition-type &format &condition #{&format byxbcdzg5oogzbei-a} make-format-condition format-condition?) (define-system-condition-type &continuation &condition #{&continuation dxr8vukkubd1tr8-a} make-continuation-condition continuation-condition? (k condition-continuation)) (define-system-condition-type $&recompile &error #{&recompile eb5ipy47b8hscnlzoga59k-0} $make-recompile-condition $recompile-condition? (importer-path $recompile-importer-path)) ) (let () (define avcond (make-assertion-violation)) (define econd (make-error)) (define wcond (make-warning)) (define fcond (make-format-condition)) (define favcond (condition avcond fcond)) (define fecond (condition econd fcond)) (define fwcond (condition wcond fcond)) (define ircond (make-implementation-restriction-violation)) (define fimpcond (condition ircond fcond)) (define flexcond (condition (make-lexical-violation) (make-i/o-read-error) fcond)) (define flexcond/ir (condition ircond (make-lexical-violation) (make-i/o-read-error) fcond)) (define (error-help warning? who whoarg message irritants basecond) (unless (or (eq? whoarg #f) (string? whoarg) (symbol? whoarg)) ($oops who "invalid who argument ~s (message = ~s, irritants = ~s)" whoarg message irritants)) (unless (string? message) ($oops who "invalid message argument ~s (who = ~s, irritants = ~s)" message whoarg irritants)) (let ([c (if whoarg (if irritants (condition basecond (make-who-condition whoarg) (make-message-condition message) (make-irritants-condition irritants)) (condition basecond (make-who-condition whoarg) (make-message-condition message))) (if irritants (condition basecond (make-message-condition message) (make-irritants-condition irritants)) (condition basecond (make-message-condition message))))]) (if warning? (raise-continuable c) (call/cc (lambda (k) (raise (condition c (make-continuation-condition k)))))))) (set-who! assertion-violation (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants avcond))) (set-who! assertion-violationf (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants favcond))) (set-who! $oops (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants favcond))) (set-who! $oops/c (lambda (whoarg basecond message . irritants) (error-help #f who whoarg message irritants (condition basecond fcond)))) (set-who! $impoops (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants fimpcond))) (set-who! $record-oops (lambda (whoarg nonrec rtd) (unless (record-type-descriptor? rtd) ($oops who "~s is not a record-type descriptor" rtd)) (when (record? nonrec rtd) ($oops who "~s actually is of type ~s" nonrec rtd)) (error-help #f who whoarg "~s is not of type ~s" (list nonrec rtd) favcond))) (set-who! error (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants econd))) (set-who! errorf (lambda (whoarg message . irritants) (error-help #f who whoarg message irritants fecond))) (set-who! warning (lambda (whoarg message . irritants) (error-help #t who whoarg message irritants wcond))) (set-who! warningf (lambda (whoarg message . irritants) (error-help #t who whoarg message irritants fwcond))) (let () (define (infer-who form) (syntax-case form () [id (identifier? #'id) (datum id)] [(id . stuff) (identifier? #'id) (datum id)] [_ #f])) (set-who! syntax-violation (case-lambda [(whoarg message form) (error-help #f who (or whoarg (infer-who form)) message #f (condition avcond (make-syntax-violation form #f)))] [(whoarg message form subform) (error-help #f who (or whoarg (infer-who form)) message #f (make-syntax-violation form subform))]))) (set-who! syntax-error (lambda (form . messages) (for-each (lambda (m) (unless (string? m) ($oops who "~s is not a string" m))) messages) (error-help #f who #f (if (null? messages) "invalid syntax" (apply string-append messages)) #f (make-syntax-violation form #f)))) (set-who! $undefined-violation (lambda (id message) (error-help #f who #f message #f (condition (make-undefined-violation) (make-syntax-violation id #f))))) (set-who! $lexical-error (case-lambda [(whoarg msg args port ir?) (error-help #f who whoarg msg args (condition (make-i/o-port-error port) (if ir? flexcond/ir flexcond)))] [(whoarg msg args port src start? ir?) (error-help #f who whoarg msg args (condition (make-i/o-port-error port) (if ir? flexcond/ir flexcond) ($make-src-condition src start?)))])) (set-who! $source-violation (lambda (whoarg src start? msg . args) (error-help #f who whoarg msg args (if src (condition favcond ($make-src-condition src start?)) favcond)))) (set-who! $source-warning (lambda (whoarg src start? msg . args) (error-help #t who whoarg msg args (if src (condition fwcond ($make-src-condition src start?)) fwcond)))) ) )