sbcl.sbcl/tests/win32-foreign-stack-unwind.impure.lisp
Nikodemus Siivola f0da2f63aa redesign exiting SBCL
Deprecate QUIT. It occupies an uncomfortable niche between processes
 and threads, and doesn't actually do what it says on the tin unless
 you call it from the main thread.

 SIGTERM now uses EXIT, and doesn't depend on sessions.

 WITH-DEADLINE (:SECONDS NIL :OVERRIDE T) can now be used to ignore
 deadlines.

 JOIN-THREAD on the main thread now blocks indefinitely instead of
 claiming the thread did not exit normally.

 New functions:

  * SB-EXT:EXIT. Always exits the process. Takes keywords :CODE,
    :ABORT, and :TIMEOUT. Code is the exit status. Abort controls if
    the exit is clean (unwind, exit-hooks, terminate other threads) or
    dirty. Timeout controls how long to wait for other threads to
    finish.

  * SB-THREAD:RETURN-FROM-THREAD. Normal termination for current
    thread -- equivalent to return from the thread function with the
    specified values. Takes keyword :ALLOW-EXIT, which determines if
    returning from the main thread is an error, or equivalent to
    calling EXIT :CODE 0.

  * SB-THREAD:ABORT-THREAD. Abnormal termination for current thread --
    equivalent to invoking the initial ABORT restart estabilished by
    MAKE-THREAD (previously known as TERMINATE-THREAD, but ANSI
    recommends there to always be an ABORT restart.) Takes keyword
    :ALLOW-EXIT, which determines if aborting the main thread is an
    error, or equivalent to calling EXIT :CODE 1.

  * SB-THREAD:MAIN-THREAD-P. Let's you determine if a given thread is
    the main thread of the process. This is important for some
    functions on some operating systems -- and RETURN-FROM-THREAD and
    ABORT-THREAD also need it.

  * SB-THREAD:MAIN-THREAD. Returns the main thread object. Convenient
    for when you need to eg. load a foreign library in the main
    thread.
2012-04-29 21:18:53 +03:00

208 lines
7.7 KiB
Common Lisp
Executable file

;;;; Testing the behavior of foreign calls trying to unwind the stack. Uses win32-stack-unwind.c.
;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; While most of SBCL is derived from the CMU CL system, the test
;;;; files (like this one) were written from scratch after the fork
;;;; from CMU CL.
;;;;
;;;; This software is in the public domain and is provided with
;;;; absolutely no warranty. See the COPYING and CREDITS files for
;;;; more information.
#-win32 (exit :code 104) ;; This is extremely win32-specific.
(use-package :sb-alien)
;;; Callbacks are not part of the exported interface yet -- when they are this can
;;; go away.
(import 'sb-alien::alien-lambda)
;;; XXX XXX this should change to use run-compiler.sh, now that we have it
(defun run-compiler ()
(let ((proc (run-program "gcc" '("win32-stack-unwind.c"
"-mno-cygwin" "-shared"
"-o" "win32-stack-unwind.dll")
:search t)))
(unless (zerop (process-exit-code proc))
(error "Bad exit code: ~S"
(process-exit-code proc)))))
(run-compiler)
(load-shared-object (truename "win32-stack-unwind.dll"))
(defvar *current-test-callback*)
(defparameter *test-callback-thunk*
(sb-alien::alien-callback
(function void)
#'(lambda () (funcall *current-test-callback*))))
(defun establish-return-frame (callback)
"Establish an SEH frame for use as a target with PERFORM-TEST-UNWIND and invoke CALLBACK via FUNCALL"
;; We don't use a LET here because we don't want to accidentally
;; correct a blown binding stack pointer just yet.
(setf *current-test-callback* callback)
(alien-funcall (extern-alien "establish_return_frame"
(function void (* (function void))))
(alien-sap *test-callback-thunk*))
(makunbound '*current-test-callback*)
(values))
(defun perform-test-unwind ()
"Perform an RtlUnwind to the surrounding ESTABLISH-RETURN-FRAME frame."
(alien-funcall (extern-alien "perform_test_unwind" (function void))))
;;; An attempt to detect and clean up latent fatalities in the
;;; post-test environent.
(defmacro with-test-environment (args &body body)
(declare (ignore args))
(let ((old-bsp (gensym))
(old-cuwp (gensym))
(old-ccb (gensym))
(old-asp (gensym)))
`(let ((*standard-input* *standard-input*))
(let ((,old-bsp (+ sb-vm::*binding-stack-pointer* 2))
(,old-cuwp sb-vm::*current-unwind-protect-block*)
(,old-ccb sb-vm:*current-catch-block*)
(,old-asp sb-vm::*alien-stack*))
(handler-case
(let ((result (progn ,@body))
extra-results)
(when (not (eql ,old-bsp sb-vm::*binding-stack-pointer*))
#+(or)
(format t "~A ~A~%" ,old-bsp sb-vm::*binding-stack-pointer*)
(push :bsp-fail extra-results))
(when (not (eql ,old-cuwp sb-vm::*current-unwind-protect-block*))
(push :cuwp-fail extra-results))
(when (not (eql ,old-ccb sb-vm:*current-catch-block*))
(push :ccb-fail extra-results))
(when (not (eql ,old-asp sb-vm::*alien-stack*))
(push :asp-fail extra-results))
(setf sb-vm::*current-unwind-protect-block* ,old-cuwp)
(setf sb-vm:*current-catch-block* ,old-ccb)
(setf sb-vm::*alien-stack* ,old-asp)
(list* result extra-results))
(error ()
:error))))))
;;; Test cases.
(with-test (:name #1=:base-case)
;; Tests that the unwind test machinery works.
(let ((result
(with-test-environment ()
(establish-return-frame (lambda () (perform-test-unwind)))
:success)))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:special-binding)
;; Tests that special bindings are undone properly during
;; unwind.
(let ((result
(with-test-environment ()
(let ((foo :success))
(declare (special foo))
(establish-return-frame (lambda ()
(let ((foo nil))
(declare (special foo))
(perform-test-unwind))))
foo))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:unwind-protect)
;; Tests that unwind-protect forms are run during unwind.
(let ((result
(with-test-environment ()
(let (result)
(establish-return-frame (lambda ()
(unwind-protect
(perform-test-unwind)
(setf result :success))))
result))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:unwind-protect-nlx)
;; Tests that unwind-protect forms that are run during unwind
;; can do a non-local exit to abort the unwind.
(let ((result
(with-test-environment ()
(let (result)
(establish-return-frame (lambda ()
(block nil
(unwind-protect
(perform-test-unwind)
(return)))
(setf result :success)))
result))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:no-unwind)
;; Basic smoke test of establish-return-frame.
(let ((result
(with-test-environment ()
(establish-return-frame (lambda ()))
:success)))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:no-unwind-error)
;; Tests that EXCEPTION_BREAKPOINT is caught and handled
;; correctly within callbacks.
(let ((result
(with-test-environment ()
(establish-return-frame (lambda ()
(handler-case
(some-undefined-function)
(undefined-function ()))))
:success)))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:unwind-foreign-frame)
;; Tests that unwinding a foreign SEH frame isn't completely
;; broken.
(let ((result
(with-test-environment ()
(block nil
(establish-return-frame (lambda () (return :success)))))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:unwind-protect-unwind-foreign-frame)
;; Tests that an unwind-protect block is allowed to unwind
;; past the original unwind target.
(let ((result
(with-test-environment ()
(block nil
(establish-return-frame (lambda ()
(unwind-protect
(perform-test-unwind)
(return :success))))))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
(with-test (:name #1=:unwind-error)
;; Another test for unwinding an SEH frame.
(let ((result
(with-test-environment ()
(handler-case
(establish-return-frame (lambda ()
(error "Foo!")))
(error ()
:success)))))
(format t "~S result: ~S~%" #1# result)
(assert (eql :success (car result)))))
;;;; success!