[commit: ghc] master: Make `catch` lazy in the action (30ee910)

git at git.haskell.org git at git.haskell.org
Fri Mar 11 12:27:05 UTC 2016


Repository : ssh://git@git.haskell.org/ghc

On branch  : master
Link       : http://ghc.haskell.org/trac/ghc/changeset/30ee9102db2f16894912e19b9d16156824611bbb/ghc

>---------------------------------------------------------------

commit 30ee9102db2f16894912e19b9d16156824611bbb
Author: Ben Gamari <bgamari.foss at gmail.com>
Date:   Fri Mar 11 10:38:10 2016 +0100

    Make `catch` lazy in the action
    
    Previously
    ```lang=haskell
    catch (error "uh oh") (\(_ :: SomeException) -> print "it failed")
    ```
    would unexpectedly fail with "uh oh" instead of the handler being run
    due to the strictness of `catch` in its first argument. See #11555 for
    details.
    
    Test Plan: Validate
    
    Reviewers: austin, hvr, simonpj
    
    Reviewed By: simonpj
    
    Subscribers: simonpj, thomie
    
    Differential Revision: https://phabricator.haskell.org/D1973
    
    GHC Trac Issues: #11555


>---------------------------------------------------------------

30ee9102db2f16894912e19b9d16156824611bbb
 libraries/base/Control/Exception/Base.hs |  2 +-
 libraries/base/GHC/IO.hs                 | 39 ++++++++++++++++++++++++++++++++
 libraries/base/tests/.gitignore          |  1 +
 libraries/base/tests/T11555.hs           |  9 ++++++++
 libraries/base/tests/T11555.stdout       |  1 +
 libraries/base/tests/all.T               |  1 +
 6 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/libraries/base/Control/Exception/Base.hs b/libraries/base/Control/Exception/Base.hs
index 351771b..5b3d47c 100644
--- a/libraries/base/Control/Exception/Base.hs
+++ b/libraries/base/Control/Exception/Base.hs
@@ -147,7 +147,7 @@ catch   :: Exception e
         => IO a         -- ^ The computation to run
         -> (e -> IO a)  -- ^ Handler to invoke if an exception is raised
         -> IO a
-catch = catchException
+catch act = catchException (lazy act)
 
 -- | The function 'catchJust' is like 'catch', but it takes an extra
 -- argument which is an /exception predicate/, a function which
diff --git a/libraries/base/GHC/IO.hs b/libraries/base/GHC/IO.hs
index 186f6c6..52a333a 100644
--- a/libraries/base/GHC/IO.hs
+++ b/libraries/base/GHC/IO.hs
@@ -126,12 +126,22 @@ Now catch# has type
 have to work around that in the definition of catchException below).
 -}
 
+-- | Catch an exception in the 'IO' monad.
+--
+-- Note that this function is /strict/ in the action. That is,
+-- @catchException undefined b == _|_ at . See #exceptions_and_strictness#
+-- for details.
 catchException :: Exception e => IO a -> (e -> IO a) -> IO a
 catchException (IO io) handler = IO $ catch# io handler'
     where handler' e = case fromException e of
                        Just e' -> unIO (handler e')
                        Nothing -> raiseIO# e
 
+-- | Catch any 'Exception' type in the 'IO' monad.
+--
+-- Note that this function is /strict/ in the action. That is,
+-- @catchException undefined b == _|_ at . See #exceptions_and_strictness# for
+-- details.
 catchAny :: IO a -> (forall e . Exception e => e -> IO a) -> IO a
 catchAny (IO io) handler = IO $ catch# io handler'
     where handler' (SomeException e) = unIO (handler e)
@@ -373,3 +383,32 @@ a `finally` sequel =
 -- use @'return' '$!' x at .
 evaluate :: a -> IO a
 evaluate a = IO $ \s -> seq# a s -- NB. see #2273, #5129
+
+{- $exceptions_and_strictness
+
+Laziness can interact with @catch at -like operations in non-obvious ways (see,
+e.g. GHC Trac #11555). For instance, consider these subtly-different examples,
+
+> test1 = Control.Exception.catch (error "uh oh") (\(_ :: SomeException) -> putStrLn "it failed")
+>
+> test2 = GHC.IO.catchException (error "uh oh") (\(_ :: SomeException) -> putStrLn "it failed")
+
+While the first case is always guaranteed to print "it failed", the behavior of
+ at test2@ may vary with optimization level.
+
+The unspecified behavior of @test2@ is due to the fact that GHC may assume that
+'catchException' (and the 'catch#' primitive operation which it is built upon)
+is strict in its first argument. This assumption allows the compiler to better
+optimize @catchException@ calls at the expense of deterministic behavior when
+the action may be bottom.
+
+Namely, the assumed strictness means that exceptions thrown while evaluating the
+action-to-be-executed may not be caught; only exceptions thrown during execution
+of the action will be handled by the exception handler.
+
+Since this strictness is a small optimization and may lead to surprising
+results, all of the @catch@ and @handle@ variants offered by "Control.Exception"
+are lazy in their first argument. If you are certain that that the action to be
+executed won't bottom in performance-sensitive code, you might consider using
+'GHC.IO.catchException' or 'GHC.IO.catchAny' for a small speed-up.
+-}
diff --git a/libraries/base/tests/.gitignore b/libraries/base/tests/.gitignore
index a430bd7..32b9d10 100644
--- a/libraries/base/tests/.gitignore
+++ b/libraries/base/tests/.gitignore
@@ -273,3 +273,4 @@
 /weak001
 /T9395
 /T9532
+/T11555
diff --git a/libraries/base/tests/T11555.hs b/libraries/base/tests/T11555.hs
new file mode 100644
index 0000000..ce5b961
--- /dev/null
+++ b/libraries/base/tests/T11555.hs
@@ -0,0 +1,9 @@
+import Control.Exception
+
+-- Ensure that catch catches exceptions thrown during the evaluation of the
+-- action-to-be-executed. This should output "it failed".
+main :: IO ()
+main = catch (error "uh oh") handler
+
+handler :: SomeException -> IO ()
+handler _ = putStrLn "it failed"
diff --git a/libraries/base/tests/T11555.stdout b/libraries/base/tests/T11555.stdout
new file mode 100644
index 0000000..2f1c27e
--- /dev/null
+++ b/libraries/base/tests/T11555.stdout
@@ -0,0 +1 @@
+it failed
diff --git a/libraries/base/tests/all.T b/libraries/base/tests/all.T
index 06ef3bb..574aba6 100644
--- a/libraries/base/tests/all.T
+++ b/libraries/base/tests/all.T
@@ -212,3 +212,4 @@ test('T9848',
       ['-O'])
 test('T10149', normal, compile_and_run, [''])
 test('T11334', normal, compile_and_run, [''])
+test('T11555', normal, compile_and_run, [''])
\ No newline at end of file



More information about the ghc-commits mailing list