1 ;;;; Testing the behavior of foreign calls trying to unwind the stack. Uses win32-stack-unwind.c.
3 ;;;; This software is part of the SBCL system. See the README file for
6 ;;;; While most of SBCL is derived from the CMU CL system, the test
7 ;;;; files (like this one) were written from scratch after the fork
10 ;;;; This software is in the public domain and is provided with
11 ;;;; absolutely no warranty. See the COPYING and CREDITS files for
12 ;;;; more information.
14 #-win32 (quit :unix-status 104) ;; This is extremely win32-specific.
16 (use-package :sb-alien)
18 ;;; Callbacks are not part of the exported interface yet -- when they are this can
20 (import 'sb-alien::alien-lambda)
22 (defun run-compiler ()
23 (let ((proc (run-program "gcc" '("win32-stack-unwind.c"
24 "-mno-cygwin" "-shared"
25 "-o" "win32-stack-unwind.dll")
27 (unless (zerop (process-exit-code proc))
28 (error "Bad exit code: ~S"
29 (process-exit-code proc)))))
33 (load-shared-object (truename "win32-stack-unwind.dll"))
36 (defvar *current-test-callback*)
38 (defparameter *test-callback-thunk*
39 (sb-alien::alien-callback
41 #'(lambda () (funcall *current-test-callback*))))
43 (defun establish-return-frame (callback)
44 "Establish an SEH frame for use as a target with PERFORM-TEST-UNWIND and invoke CALLBACK via FUNCALL"
45 ;; We don't use a LET here because we don't want to accidentally
46 ;; correct a blown binding stack pointer just yet.
47 (setf *current-test-callback* callback)
48 (alien-funcall (extern-alien "establish_return_frame"
49 (function void (* (function void))))
50 (alien-sap *test-callback-thunk*))
51 (makunbound '*current-test-callback*)
54 (defun perform-test-unwind ()
55 "Perform an RtlUnwind to the surrounding ESTABLISH-RETURN-FRAME frame."
56 (alien-funcall (extern-alien "perform_test_unwind" (function void))))
59 ;;; An attempt to detect and clean up latent fatalities in the
60 ;;; post-test environent.
62 (defmacro with-test-environment (args &body body)
63 (declare (ignore args))
64 (let ((old-bsp (gensym))
68 `(let ((*standard-input* *standard-input*))
69 (let ((,old-bsp (+ sb-vm::*binding-stack-pointer* 2))
70 (,old-cuwp sb-vm::*current-unwind-protect-block*)
71 (,old-ccb sb-vm:*current-catch-block*)
72 (,old-asp sb-vm::*alien-stack*))
74 (let ((result (progn ,@body))
76 (when (not (eql ,old-bsp sb-vm::*binding-stack-pointer*))
78 (format t "~A ~A~%" ,old-bsp sb-vm::*binding-stack-pointer*)
79 (push :bsp-fail extra-results))
80 (when (not (eql ,old-cuwp sb-vm::*current-unwind-protect-block*))
81 (push :cuwp-fail extra-results))
82 (when (not (eql ,old-ccb sb-vm:*current-catch-block*))
83 (push :ccb-fail extra-results))
84 (when (not (eql ,old-asp sb-vm::*alien-stack*))
85 (push :asp-fail extra-results))
86 (setf sb-vm::*current-unwind-protect-block* ,old-cuwp)
87 (setf sb-vm:*current-catch-block* ,old-ccb)
88 (setf sb-vm::*alien-stack* ,old-asp)
89 (list* result extra-results))
96 (with-test (:name #1=:base-case)
97 ;; Tests that the unwind test machinery works.
99 (with-test-environment ()
100 (establish-return-frame (lambda () (perform-test-unwind)))
102 (format t "~S result: ~S~%" #1# result)
103 (assert (eql :success (car result)))))
105 (with-test (:name #1=:special-binding)
106 ;; Tests that special bindings are undone properly during
109 (with-test-environment ()
110 (let ((foo :success))
111 (declare (special foo))
112 (establish-return-frame (lambda ()
114 (declare (special foo))
115 (perform-test-unwind))))
117 (format t "~S result: ~S~%" #1# result)
118 (assert (eql :success (car result)))))
120 (with-test (:name #1=:unwind-protect)
121 ;; Tests that unwind-protect forms are run during unwind.
123 (with-test-environment ()
125 (establish-return-frame (lambda ()
127 (perform-test-unwind)
128 (setf result :success))))
130 (format t "~S result: ~S~%" #1# result)
131 (assert (eql :success (car result)))))
133 (with-test (:name #1=:unwind-protect-nlx)
134 ;; Tests that unwind-protect forms that are run during unwind
135 ;; can do a non-local exit to abort the unwind.
137 (with-test-environment ()
139 (establish-return-frame (lambda ()
142 (perform-test-unwind)
144 (setf result :success)))
146 (format t "~S result: ~S~%" #1# result)
147 (assert (eql :success (car result)))))
149 (with-test (:name #1=:no-unwind)
150 ;; Basic smoke test of establish-return-frame.
152 (with-test-environment ()
153 (establish-return-frame (lambda ()))
155 (format t "~S result: ~S~%" #1# result)
156 (assert (eql :success (car result)))))
158 (with-test (:name #1=:no-unwind-error)
159 ;; Tests that EXCEPTION_BREAKPOINT is caught and handled
160 ;; correctly within callbacks.
162 (with-test-environment ()
163 (establish-return-frame (lambda ()
165 (some-undefined-function)
166 (undefined-function ()))))
168 (format t "~S result: ~S~%" #1# result)
169 (assert (eql :success (car result)))))
171 (with-test (:name #1=:unwind-foreign-frame)
172 ;; Tests that unwinding a foreign SEH frame isn't completely
175 (with-test-environment ()
177 (establish-return-frame (lambda () (return :success)))))))
178 (format t "~S result: ~S~%" #1# result)
179 (assert (eql :success (car result)))))
181 (with-test (:name #1=:unwind-protect-unwind-foreign-frame)
182 ;; Tests that an unwind-protect block is allowed to unwind
183 ;; past the original unwind target.
185 (with-test-environment ()
187 (establish-return-frame (lambda ()
189 (perform-test-unwind)
190 (return :success))))))))
191 (format t "~S result: ~S~%" #1# result)
192 (assert (eql :success (car result)))))
194 (with-test (:name #1=:unwind-error)
195 ;; Another test for unwinding an SEH frame.
197 (with-test-environment ()
199 (establish-return-frame (lambda ()
203 (format t "~S result: ~S~%" #1# result)
204 (assert (eql :success (car result)))))