error-handling.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. ;;; Catching errors.
  2. ;;; Copyright (C) 2024 Igalia, S.L.
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; with-exception-handler, guard, and all that.
  18. ;;;
  19. ;;; Code:
  20. (library (hoot error-handling)
  21. (export guard format-exception capture-stack stack-height print-backtrace)
  22. (import (hoot cond-expand)
  23. (hoot pairs)
  24. (hoot eq)
  25. (hoot errors)
  26. (hoot exceptions)
  27. (hoot fluids)
  28. (hoot inline-wasm)
  29. (only (hoot control)
  30. make-prompt-tag call-with-prompt abort-to-prompt)
  31. (hoot match)
  32. (hoot not)
  33. (hoot numbers)
  34. (hoot ports)
  35. (hoot procedures)
  36. (hoot records)
  37. (hoot strings)
  38. (hoot syntax)
  39. (hoot values)
  40. (hoot vectors)
  41. (hoot write))
  42. ;; Snarfed from Guile's (ice-9 exceptions). Deviates a bit from R7RS.
  43. (define-syntax guard
  44. (lambda (stx)
  45. (define (dispatch tag exn clauses)
  46. (define (build-clause test handler clauses)
  47. #`(let ((t #,test))
  48. (if t
  49. (abort-to-prompt #,tag #,handler t)
  50. #,(dispatch tag exn clauses))))
  51. (syntax-case clauses (=> else)
  52. (() #`(raise-continuable #,exn))
  53. (((test => f) . clauses)
  54. (build-clause #'test #'(lambda (res) (f res)) #'clauses))
  55. (((else e e* ...) . clauses)
  56. (build-clause #'#t #'(lambda (res) e e* ...) #'clauses))
  57. (((test) . clauses)
  58. (build-clause #'test #'(lambda (res) res) #'clauses))
  59. (((test e* ...) . clauses)
  60. (build-clause #'test #'(lambda (res) e* ...) #'clauses))))
  61. (syntax-case stx ()
  62. ((guard (exn clause clause* ...) body body* ...)
  63. (identifier? #'exn)
  64. #`(let ((tag (make-prompt-tag)))
  65. (call-with-prompt
  66. tag
  67. (lambda ()
  68. (with-exception-handler
  69. (lambda (exn)
  70. #,(dispatch #'tag #'exn #'(clause clause* ...)))
  71. (lambda () body body* ...)))
  72. (lambda (_ h v)
  73. (h v))))))))
  74. (define (format-exception exception port)
  75. (display "Scheme error:\n")
  76. (match (simple-exceptions exception)
  77. (() (display "Empty exception object" port))
  78. (components
  79. (let loop ((i 1) (components components))
  80. (define (format-numbered-exception exception)
  81. (display " " port)
  82. (display i port)
  83. (display ". " port)
  84. (write exception port))
  85. (match components
  86. ((component)
  87. (format-numbered-exception component))
  88. ((component . rest)
  89. (format-numbered-exception component)
  90. (newline port)
  91. (loop (+ i 1) rest)))))))
  92. ;; A macro so as to avoid adding any stack frames.
  93. (define-syntax-rule (stack-height)
  94. (cond-expand
  95. (guile-vm
  96. (raise (make-unimplemented-error 'stack-height)))
  97. (hoot
  98. (%inline-wasm
  99. '(func (result (ref eq))
  100. (call $i32->scm (global.get $ret-sp)))))))
  101. (define (capture-stack height)
  102. (cond-expand
  103. (guile-vm
  104. (raise (make-unimplemented-error 'capture-stack)))
  105. (hoot
  106. (define (stack-ref n)
  107. (%inline-wasm
  108. '(func (param $n i32)
  109. (result (ref eq))
  110. (struct.new $code-ref (i32.const 0)
  111. (ref.as_non_null
  112. (table.get $ret-stack (local.get $n)))))
  113. n))
  114. (define stack (make-vector (min height (stack-height)) #f))
  115. (let lp ((i 0))
  116. (when (< i (vector-length stack))
  117. (vector-set! stack i (stack-ref i))
  118. (lp (1+ i))))
  119. stack)))
  120. (define* (print-backtrace stack origin file line column port #:optional
  121. (end (vector-length stack)))
  122. (cond-expand
  123. (guile-vm
  124. (raise (make-unimplemented-error 'print-backtrace)))
  125. (hoot
  126. (define (code-source code)
  127. (%inline-wasm
  128. '(func (param $code (ref func))
  129. (result (ref eq) (ref eq) (ref eq))
  130. (local $maybe-string (ref null string))
  131. (local $i1 i32)
  132. (local $i2 i32)
  133. (call $code-source (local.get $code))
  134. (local.set $i2)
  135. (local.set $i1)
  136. (local.set $maybe-string)
  137. (if (ref eq)
  138. (ref.is_null (local.get $maybe-string))
  139. (then (ref.i31 (i32.const 1)))
  140. (else (struct.new $string (i32.const 0)
  141. (ref.as_non_null (local.get $maybe-string)))))
  142. (call $i32->scm (local.get $i1))
  143. (call $i32->scm (local.get $i2)))
  144. code))
  145. (define (code-name code)
  146. (%inline-wasm
  147. '(func (param $code (ref func)) (result (ref eq))
  148. (local $maybe-string (ref null string))
  149. (call $code-name (local.get $code))
  150. (local.set $maybe-string)
  151. (if (ref eq)
  152. (ref.is_null (local.get $maybe-string))
  153. (then (ref.i31 (i32.const 1)))
  154. (else (struct.new $string (i32.const 0)
  155. (ref.as_non_null (local.get $maybe-string))))))
  156. code))
  157. (define (print-file file)
  158. (match file
  159. (#f
  160. (write-string "In unknown file:\n" port))
  161. (file
  162. (write-string "In " port)
  163. (write-string file port)
  164. (write-string ":\n" port)))
  165. (flush-output-port port))
  166. (define (print-frame line col idx proc-name)
  167. (define (left-pad str size)
  168. (if (< (string-length str) size)
  169. (string-append (make-string (- size (string-length str))
  170. #\space)
  171. str)
  172. str))
  173. (define (right-pad str size)
  174. (if (< (string-length str) size)
  175. (string-append str
  176. (make-string (- size (string-length str))
  177. #\space))
  178. str))
  179. (cond
  180. ((and line col)
  181. (write-string (string-append
  182. (left-pad (number->string line) 6)
  183. ":"
  184. (right-pad (number->string col) 3)
  185. " ")
  186. port))
  187. (else
  188. (write-string " " port)))
  189. (write-string (left-pad (number->string idx) 3) port)
  190. (write-string " (" port)
  191. (write (or proc-name "_") port)
  192. (write-string " …)" port)
  193. (newline port)
  194. (flush-output-port port))
  195. (write-string "Hoot backtrace:\n" port)
  196. (define (same-files? a b)
  197. (if a
  198. (and b (string=? a b))
  199. (not b)))
  200. (let lp ((i 0) (previous-file #f))
  201. (cond
  202. ((< i (vector-length stack))
  203. (call-with-values (lambda () (code-source (vector-ref stack i)))
  204. (lambda (file line col)
  205. (define name (code-name (vector-ref stack i)))
  206. (unless (and (not (zero? i))
  207. (same-files? file previous-file))
  208. (print-file file))
  209. (if (zero? line)
  210. (print-frame #f #f (- (vector-length stack) i) name)
  211. (print-frame line col (- (vector-length stack) i) name))
  212. (lp (1+ i) file))))
  213. (else
  214. (unless (same-files? file previous-file)
  215. (print-file file))
  216. (print-frame line column 0 origin)))))))
  217. (cond-expand
  218. (guile-vm)
  219. (hoot-main
  220. (let ()
  221. (define %exception-handler (make-fluid #f))
  222. (define (fluid-ref* fluid depth)
  223. (%inline-wasm
  224. '(func (param $fluid (ref $fluid)) (param $depth i32)
  225. (result (ref eq))
  226. (call $fluid-ref* (local.get $fluid) (local.get $depth)))
  227. fluid depth))
  228. (define* (with-exception-handler handler thunk #:key (unwind? #f) (unwind-for-type #t))
  229. (check-type handler procedure? 'with-exception-handler)
  230. (cond
  231. (unwind?
  232. (let ((tag (make-prompt-tag "exception handler")))
  233. (call-with-prompt
  234. tag
  235. (lambda ()
  236. (with-fluids ((%exception-handler (cons unwind-for-type tag)))
  237. (thunk)))
  238. (lambda (k exn)
  239. (handler exn)))))
  240. (else
  241. (let ((running? (make-fluid #f)))
  242. (with-fluids ((%exception-handler (cons running? handler)))
  243. (thunk))))))
  244. (define (raise-non-continuable-exception)
  245. (raise (make-exception (make-non-continuable-violation)
  246. (make-exception-with-message
  247. "unhandled non-continuable exception"))))
  248. (define (fallback-exception-handler exn captured-height)
  249. (define stack (capture-stack captured-height))
  250. (define port (current-error-port))
  251. (define origin
  252. (and (exception-with-origin? exn) (exception-origin exn)))
  253. (call-with-values (lambda ()
  254. (if (exception-with-source? exn)
  255. (values (exception-source-file exn)
  256. (exception-source-line exn)
  257. (exception-source-column exn))
  258. (values #f #f #f)))
  259. (lambda (file line column)
  260. (print-backtrace stack origin file line column port port)
  261. (write-string "\nUncaught exception:\n" port)
  262. (format-exception exn port)
  263. (newline port)
  264. (flush-output-port port)
  265. (%inline-wasm
  266. '(func (param $status i32)
  267. (call $quit (local.get $status))
  268. (unreachable))
  269. 1))))
  270. (define* (raise-exception exn #:key continuable?)
  271. (define captured-stack-height (stack-height))
  272. (define (is-a? x type)
  273. (let ((vtable (%inline-wasm
  274. '(func (param $x (ref $struct)) (result (ref eq))
  275. (ref.as_non_null
  276. (struct.get $struct $vtable (local.get $x))))
  277. x)))
  278. (or (eq? vtable type)
  279. (let ((parents (record-type-parents vtable)))
  280. (let lp ((i 0))
  281. (and (< i (vector-length parents))
  282. (or (eq? (vector-ref parents i) type)
  283. (lp (1+ i)))))))))
  284. (define (exception-has-type? exn type)
  285. (cond
  286. ((eq? type #t) #t)
  287. ((exception? exn)
  288. (or (is-a? exn type)
  289. (and (compound-exception? exn)
  290. (let lp ((simple (compound-exception-components exn)))
  291. (match simple
  292. (() #f)
  293. ((x . rest)
  294. (or (is-a? x type) (lp rest))))))))
  295. (else #f)))
  296. (let lp ((depth 0))
  297. ;; FIXME: fluid-ref* takes time proportional to depth, which
  298. ;; makes this loop quadratic.
  299. (match (fluid-ref* %exception-handler depth)
  300. (#f
  301. (fallback-exception-handler exn captured-stack-height))
  302. (((? fluid? running?) . handler)
  303. (if (fluid-ref running?)
  304. (lp (1+ depth))
  305. (with-fluids ((running? #t))
  306. (cond
  307. (continuable?
  308. (handler exn))
  309. (else
  310. (handler exn)
  311. (raise-non-continuable-exception))))))
  312. ((type . prompt-tag)
  313. (cond
  314. ((exception-has-type? exn type)
  315. (abort-to-prompt prompt-tag exn)
  316. (raise-non-continuable-exception))
  317. (else
  318. (lp (1+ depth))))))))
  319. (define-syntax-rule (initialize-globals (global type proc) ...)
  320. (%inline-wasm
  321. '(func (param global type) ...
  322. (global.set global (local.get global)) ...)
  323. proc ...))
  324. (define-syntax-rule (initialize-proc-globals (global proc) ...)
  325. (initialize-globals (global (ref $proc) proc) ...))
  326. (initialize-proc-globals
  327. ($with-exception-handler with-exception-handler)
  328. ($raise-exception raise-exception))))
  329. (hoot-aux)))