Hello community,

here is the log from the commit of package ghc-yesod-core for openSUSE:Factory 
checked in at 2016-07-07 15:10:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ghc-yesod-core (Old)
 and      /work/SRC/openSUSE:Factory/.ghc-yesod-core.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ghc-yesod-core"

Changes:
--------
--- /work/SRC/openSUSE:Factory/ghc-yesod-core/ghc-yesod-core.changes    
2016-07-01 09:58:53.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.ghc-yesod-core.new/ghc-yesod-core.changes       
2016-07-07 15:10:29.000000000 +0200
@@ -1,0 +2,6 @@
+Thu Jun 30 13:28:15 UTC 2016 - mimi...@gmail.com
+
+- update to 1.4.22
+* Proper handling of impure exceptions within HandlerError values
+
+-------------------------------------------------------------------

Old:
----
  yesod-core-1.4.21.tar.gz

New:
----
  yesod-core-1.4.22.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ghc-yesod-core.spec ++++++
--- /var/tmp/diff_new_pack.GNQHCg/_old  2016-07-07 15:10:31.000000000 +0200
+++ /var/tmp/diff_new_pack.GNQHCg/_new  2016-07-07 15:10:31.000000000 +0200
@@ -19,7 +19,7 @@
 %global pkg_name yesod-core
 %bcond_with tests
 Name:           ghc-%{pkg_name}
-Version:        1.4.21
+Version:        1.4.22
 Release:        0
 Summary:        Creation of type-safe, RESTful web applications
 License:        MIT
@@ -44,6 +44,7 @@
 BuildRequires:  ghc-cookie-devel
 BuildRequires:  ghc-data-default-devel
 BuildRequires:  ghc-deepseq-devel
+BuildRequires:  ghc-deepseq-generics-devel
 BuildRequires:  ghc-directory-devel
 BuildRequires:  ghc-exceptions-devel
 BuildRequires:  ghc-fast-logger-devel

++++++ yesod-core-1.4.21.tar.gz -> yesod-core-1.4.22.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yesod-core-1.4.21/ChangeLog.md 
new/yesod-core-1.4.22/ChangeLog.md
--- old/yesod-core-1.4.21/ChangeLog.md  2016-06-20 16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/ChangeLog.md  2016-06-27 09:45:17.000000000 +0200
@@ -1,3 +1,7 @@
+## 1.4.22
+
+* Proper handling of impure exceptions within `HandlerError` values
+
 ## 1.4.21
 
 * Add support for `Encoding` from `aeson-0.11` 
[#1241](https://github.com/yesodweb/yesod/pull/1241)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yesod-core-1.4.21/Yesod/Core/Internal/Run.hs 
new/yesod-core-1.4.22/Yesod/Core/Internal/Run.hs
--- old/yesod-core-1.4.21/Yesod/Core/Internal/Run.hs    2016-06-20 
16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/Yesod/Core/Internal/Run.hs    2016-06-27 
09:45:17.000000000 +0200
@@ -4,43 +4,36 @@
 {-# LANGUAGE RankNTypes        #-}
 {-# LANGUAGE RecordWildCards   #-}
 {-# LANGUAGE TemplateHaskell   #-}
+{-# LANGUAGE TupleSections     #-}
 {-# LANGUAGE FlexibleContexts  #-}
 module Yesod.Core.Internal.Run where
 
 
 #if __GLASGOW_HASKELL__ < 710
-import           Data.Monoid                  (mempty)
+import           Data.Monoid                  (Monoid, mempty)
 #endif
 import Yesod.Core.Internal.Response
 import           Blaze.ByteString.Builder     (toByteString)
 import           Control.Exception            (fromException, evaluate)
 import qualified Control.Exception            as E
-import           Control.Exception.Lifted     (catch)
-import           Control.Monad                (mplus)
-import           Control.Monad.IO.Class       (MonadIO)
-import           Control.Monad.IO.Class       (liftIO)
+import           Control.Monad.IO.Class       (MonadIO, liftIO)
 import           Control.Monad.Logger         (LogLevel (LevelError), 
LogSource,
                                                liftLoc)
-import           Control.Monad.Trans.Resource (runResourceT, 
withInternalState, runInternalState)
+import           Control.Monad.Trans.Resource (runResourceT, 
withInternalState, runInternalState, InternalState)
 import qualified Data.ByteString              as S
 import qualified Data.ByteString.Char8        as S8
 import qualified Data.IORef                   as I
 import qualified Data.Map                     as Map
-import           Data.Maybe                   (isJust)
-import           Data.Maybe                   (fromMaybe)
+import           Data.Maybe                   (isJust, fromMaybe)
 import           Data.Monoid                  (appEndo)
 import           Data.Text                    (Text)
 import qualified Data.Text                    as T
-import           Data.Text.Encoding           (encodeUtf8)
-import           Data.Text.Encoding           (decodeUtf8With)
+import           Data.Text.Encoding           (encodeUtf8, decodeUtf8With)
 import           Data.Text.Encoding.Error     (lenientDecode)
 import           Language.Haskell.TH.Syntax   (Loc, qLocation)
 import qualified Network.HTTP.Types           as H
 import           Network.Wai
 import           Network.Wai.Internal
-#if !MIN_VERSION_base(4, 6, 0)
-import           Prelude                      hiding (catch)
-#endif
 import           System.Log.FastLogger        (LogStr, toLogStr)
 import           Yesod.Core.Content
 import           Yesod.Core.Class.Yesod
@@ -49,31 +42,73 @@
                                                tooLargeResponse)
 import           Yesod.Core.Internal.Util     (getCurrentMaxExpiresRFC1123)
 import           Yesod.Routes.Class           (Route, renderRoute)
-import           Control.DeepSeq              (($!!))
+import           Control.DeepSeq              (($!!), NFData)
 
-returnDeepSessionMap :: Monad m => SessionMap -> m SessionMap
-#if MIN_VERSION_bytestring(0, 10, 0)
-returnDeepSessionMap sm = return $!! sm
-#else
-returnDeepSessionMap sm = fmap unWrappedBS `liftM` (return $!! fmap WrappedBS 
sm)
-
--- | Work around missing NFData instance for bytestring 0.9.
-newtype WrappedBS = WrappedBS { unWrappedBS :: S8.ByteString }
-instance NFData WrappedBS
-#endif
+-- | Catch all synchronous exceptions, ignoring asynchronous
+-- exceptions.
+--
+-- Ideally we'd use this from a different library
+catchSync :: IO a -> (E.SomeException -> IO a) -> IO a
+catchSync thing after = thing `E.catch` \e ->
+    if isAsyncException e
+        then E.throwIO e
+        else after e
 
--- | Function used internally by Yesod in the process of converting a
--- 'HandlerT' into an 'Application'. Should not be needed by users.
-runHandler :: ToTypedContent c
-           => RunHandlerEnv site
-           -> HandlerT site IO c
-           -> YesodApp
-runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState 
-> do
-    let toErrorHandler e =
+-- | Determine if an exception is asynchronous
+--
+-- Also worth being upstream
+isAsyncException :: E.SomeException -> Bool
+isAsyncException e =
+    case fromException e of
+        Just E.SomeAsyncException{} -> True
+        Nothing -> False
+
+-- | Convert an exception into an ErrorResponse
+toErrorHandler :: E.SomeException -> IO ErrorResponse
+toErrorHandler e0 = flip catchSync errFromShow $
+    case fromException e0 of
+        Just (HCError x) -> evaluate $!! x
+        _
+            | isAsyncException e0 -> E.throwIO e0
+            | otherwise -> errFromShow e0
+
+-- | Generate an @ErrorResponse@ based on the shown version of the exception
+errFromShow :: E.SomeException -> IO ErrorResponse
+errFromShow x = evaluate $!! InternalError $! T.pack $! show x
+
+-- | Do a basic run of a handler, getting some contents and the final
+-- @GHState@. The @GHState@ unfortunately may contain some impure
+-- exceptions, but all other synchronous exceptions will be caught and
+-- represented by the @HandlerContents@.
+basicRunHandler :: ToTypedContent c
+                => RunHandlerEnv site
+                -> HandlerT site IO c
+                -> YesodRequest
+                -> InternalState
+                -> IO (GHState, HandlerContents)
+basicRunHandler rhe handler yreq resState = do
+    -- Create a mutable ref to hold the state. We use mutable refs so
+    -- that the updates will survive runtime exceptions.
+    istate <- I.newIORef defState
+
+    -- Run the handler itself, capturing any runtime exceptions and
+    -- converting them into a @HandlerContents@
+    contents' <- catchSync
+        (do
+            res <- unHandlerT handler (hd istate)
+            tc <- evaluate (toTypedContent res)
+            -- Success! Wrap it up in an @HCContent@
+            return (HCContent defaultStatus tc))
+        (\e ->
             case fromException e of
-                Just (HCError x) -> x
-                _ -> InternalError $ T.pack $ show e
-    istate <- liftIO $ I.newIORef GHState
+                Just e' -> return e'
+                Nothing -> fmap HCError $ toErrorHandler e)
+
+    -- Get the raw state and return
+    state <- I.readIORef istate
+    return (state, contents')
+  where
+    defState = GHState
         { ghsSession = reqSession yreq
         , ghsRBC = Nothing
         , ghsIdent = 1
@@ -81,56 +116,57 @@
         , ghsCacheBy = mempty
         , ghsHeaders = mempty
         }
-    let hd = HandlerData
-            { handlerRequest = yreq
-            , handlerEnv     = rhe
-            , handlerState   = istate
-            , handlerToParent = const ()
-            , handlerResource = resState
+    hd istate = HandlerData
+        { handlerRequest = yreq
+        , handlerEnv     = rhe
+        , handlerState   = istate
+        , handlerToParent = const ()
+        , handlerResource = resState
+        }
+
+-- | Convert an @ErrorResponse@ into a @YesodResponse@
+handleError :: RunHandlerEnv site
+            -> YesodRequest
+            -> InternalState
+            -> Map.Map Text S8.ByteString
+            -> [Header]
+            -> ErrorResponse
+            -> IO YesodResponse
+handleError rhe yreq resState finalSession headers e0 = do
+    -- Find any evil hidden impure exceptions
+    e <- (evaluate $!! e0) `catchSync` errFromShow
+
+    -- Generate a response, leveraging the updated session and
+    -- response headers
+    flip runInternalState resState $ do
+        yar <- rheOnError rhe e yreq
+            { reqSession = finalSession
             }
-    contents' <- catch (fmap Right $ unHandlerT handler hd)
-        (\e -> return $ Left $ maybe (HCError $ toErrorHandler e) id
-                      $ fromException e)
-    state <- liftIO $ I.readIORef istate
-
-    (finalSession, mcontents1) <- (do
-        finalSession <- returnDeepSessionMap (ghsSession state)
-        return (finalSession, Nothing)) `E.catch` \e -> return
-            (Map.empty, Just $! HCError $! InternalError $! T.pack $! show (e 
:: E.SomeException))
-
-    (headers, mcontents2) <- (do
-        headers <- return $!! appEndo (ghsHeaders state) []
-        return (headers, Nothing)) `E.catch` \e -> return
-            ([], Just $! HCError $! InternalError $! T.pack $! show (e :: 
E.SomeException))
-
-    let contents =
-            case mcontents1 `mplus` mcontents2 of
-                Just x -> x
-                Nothing -> either id (HCContent defaultStatus . 
toTypedContent) contents'
-    let handleError e = flip runInternalState resState $ do
-            yar <- rheOnError e yreq
-                { reqSession = finalSession
-                }
-            case yar of
-                YRPlain status' hs ct c sess ->
-                    let hs' = headers ++ hs
-                        status
-                            | status' == defaultStatus = getStatus e
-                            | otherwise = status'
-                     in return $ YRPlain status hs' ct c sess
-                YRWai _ -> return yar
-                YRWaiApp _ -> return yar
-    let sendFile' ct fp p =
-            return $ YRPlain H.status200 headers ct (ContentFile fp p) 
finalSession
-    contents1 <- evaluate contents `E.catch` \e -> return
-        (HCError $! InternalError $! T.pack $! show (e :: E.SomeException))
-    case contents1 of
+        case yar of
+            YRPlain status' hs ct c sess ->
+                let hs' = headers ++ hs
+                    status
+                        | status' == defaultStatus = getStatus e
+                        | otherwise = status'
+                in return $ YRPlain status hs' ct c sess
+            YRWai _ -> return yar
+            YRWaiApp _ -> return yar
+
+-- | Convert a @HandlerContents@ into a @YesodResponse@
+handleContents :: (ErrorResponse -> IO YesodResponse)
+               -> Map.Map Text S8.ByteString
+               -> [Header]
+               -> HandlerContents
+               -> IO YesodResponse
+handleContents handleError' finalSession headers contents =
+    case contents of
         HCContent status (TypedContent ct c) -> do
-            ec' <- liftIO $ evaluateContent c
+            -- Check for impure exceptions hiding in the contents
+            ec' <- evaluateContent c
             case ec' of
-                Left e -> handleError e
+                Left e -> handleError' e
                 Right c' -> return $ YRPlain status headers ct c' finalSession
-        HCError e -> handleError e
+        HCError e -> handleError' e
         HCRedirect status loc -> do
             let disable_caching x =
                       Header "Cache-Control" "no-cache, must-revalidate"
@@ -141,20 +177,54 @@
             return $ YRPlain
                 status hs typePlain emptyContent
                 finalSession
-        HCSendFile ct fp p -> catch
-            (sendFile' ct fp p)
-            (handleError . toErrorHandler)
-        HCCreated loc -> do
-            let hs = Header "Location" (encodeUtf8 loc) : headers
-            return $ YRPlain
-                H.status201
-                hs
-                typePlain
-                emptyContent
-                finalSession
+        HCSendFile ct fp p -> return $ YRPlain
+            H.status200
+            headers
+            ct
+            (ContentFile fp p)
+            finalSession
+        HCCreated loc -> return $ YRPlain
+            H.status201
+            (Header "Location" (encodeUtf8 loc) : headers)
+            typePlain
+            emptyContent
+            finalSession
         HCWai r -> return $ YRWai r
         HCWaiApp a -> return $ YRWaiApp a
 
+-- | Evaluate the given value. If an exception is thrown, use it to
+-- replace the provided contents and then return @mempty@ in place of the
+-- evaluated value.
+evalFallback :: (Monoid w, NFData w)
+             => HandlerContents
+             -> w
+             -> IO (w, HandlerContents)
+evalFallback contents val = catchSync
+    (fmap (, contents) (evaluate $!! val))
+    (fmap ((mempty, ) . HCError) . toErrorHandler)
+
+-- | Function used internally by Yesod in the process of converting a
+-- 'HandlerT' into an 'Application'. Should not be needed by users.
+runHandler :: ToTypedContent c
+           => RunHandlerEnv site
+           -> HandlerT site IO c
+           -> YesodApp
+runHandler rhe@RunHandlerEnv {..} handler yreq = withInternalState $ \resState 
-> do
+    -- Get the raw state and original contents
+    (state, contents0) <- basicRunHandler rhe handler yreq resState
+
+    -- Evaluate the unfortunately-lazy session and headers,
+    -- propagating exceptions into the contents
+    (finalSession, contents1) <- evalFallback contents0 (ghsSession state)
+    (headers, contents2) <- evalFallback contents1 (appEndo (ghsHeaders state) 
[])
+
+    -- Convert the HandlerContents into the final YesodResponse
+    handleContents
+        (handleError rhe yreq resState finalSession headers)
+        finalSession
+        headers
+        contents2
+
 safeEh :: (Loc -> LogSource -> LogLevel -> LogStr -> IO ())
        -> ErrorResponse
        -> YesodApp
@@ -197,8 +267,7 @@
 runFakeHandler fakeSessionMap logger site handler = liftIO $ do
   ret <- I.newIORef (Left $ InternalError "runFakeHandler: no result")
   maxExpires <- getCurrentMaxExpiresRFC1123
-  let handler' = do liftIO . I.writeIORef ret . Right =<< handler
-                    return ()
+  let handler' = liftIO . I.writeIORef ret . Right =<< handler
   let yapp = runHandler
          RunHandlerEnv
             { rheRender = yesodRender site $ resolveApproot site fakeWaiRequest
@@ -233,6 +302,10 @@
           , vault          = mempty
           , requestBodyLength = KnownLength 0
           , requestHeaderRange = Nothing
+#if MIN_VERSION_wai(3,2,0)
+          , requestHeaderReferer = Nothing
+          , requestHeaderUserAgent = Nothing
+#endif
           }
       fakeRequest =
         YesodRequest
@@ -256,7 +329,7 @@
   | Just maxLen <- mmaxLen, KnownLength len <- requestBodyLength req, maxLen < 
len = sendResponse tooLargeResponse
   | otherwise = do
     let dontSaveSession _ = return []
-    (session, saveSession) <- liftIO $ do
+    (session, saveSession) <- liftIO $
         maybe (return (Map.empty, dontSaveSession)) (\sb -> sbLoadSession sb 
req) yreSessionBackend
     maxExpires <- yreGetMaxExpires
     let mkYesodReq = parseWaiRequest req session (isJust yreSessionBackend) 
mmaxLen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yesod-core-1.4.21/Yesod/Core/Types.hs 
new/yesod-core-1.4.22/Yesod/Core/Types.hs
--- old/yesod-core-1.4.21/Yesod/Core/Types.hs   2016-06-20 16:31:06.000000000 
+0200
+++ new/yesod-core-1.4.22/Yesod/Core/Types.hs   2016-06-27 09:45:17.000000000 
+0200
@@ -1,5 +1,6 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DeriveDataTypeable         #-}
+{-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE TupleSections #-}
 {-# LANGUAGE FlexibleInstances          #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -41,6 +42,7 @@
 import qualified Data.Text.Lazy.Builder             as TBuilder
 import           Data.Time                          (UTCTime)
 import           Data.Typeable                      (Typeable)
+import           GHC.Generics                       (Generic)
 import           Language.Haskell.TH.Syntax         (Loc)
 import qualified Network.HTTP.Types                 as H
 import           Network.Wai                        (FilePart,
@@ -62,6 +64,7 @@
 import Prelude hiding (catch)
 #endif
 import Control.DeepSeq (NFData (rnf))
+import Control.DeepSeq.Generics (genericRnf)
 import Data.Conduit.Lazy (MonadActive, monadActive)
 import Yesod.Core.TypeCache (TypeMap, KeyedTypeMap)
 #if MIN_VERSION_monad_logger(0, 3, 10)
@@ -314,7 +317,9 @@
     | NotAuthenticated
     | PermissionDenied Text
     | BadMethod H.Method
-    deriving (Show, Eq, Typeable)
+    deriving (Show, Eq, Typeable, Generic)
+instance NFData ErrorResponse where
+    rnf = genericRnf
 
 ----- header stuff
 -- | Headers to be added to a 'Result'.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yesod-core-1.4.21/test/YesodCoreTest/Exceptions.hs 
new/yesod-core-1.4.22/test/YesodCoreTest/Exceptions.hs
--- old/yesod-core-1.4.21/test/YesodCoreTest/Exceptions.hs      2016-06-20 
16:31:06.000000000 +0200
+++ new/yesod-core-1.4.22/test/YesodCoreTest/Exceptions.hs      2016-06-27 
09:45:17.000000000 +0200
@@ -6,6 +6,8 @@
 import Test.Hspec
 
 import Yesod.Core
+import Yesod.Core.Types (HandlerContents (HCError))
+import Control.Exception (throwIO)
 import Network.Wai
 import Network.Wai.Test
 import Network.HTTP.Types (status301)
@@ -14,11 +16,15 @@
 mkYesod "Y" [parseRoutes|
 / RootR GET
 /redirect RedirR GET
+/impure ImpureR GET
 |]
 
 instance Yesod Y where
     approot = ApprootStatic "http://test";
-    errorHandler (InternalError e) = return $ toTypedContent e
+    errorHandler (InternalError e) = do
+        _ <- return $! e
+        addHeader "ERROR" "HANDLER"
+        return $ toTypedContent e
     errorHandler x = defaultErrorHandler x
 
 getRootR :: Handler ()
@@ -29,10 +35,14 @@
     addHeader "foo" "bar"
     redirectWith status301 RootR
 
+getImpureR :: Handler ()
+getImpureR = liftIO $ throwIO $ HCError $ InternalError $ error "impure!"
+
 exceptionsTest :: Spec
 exceptionsTest = describe "Test.Exceptions" $ do
       it "500" case500
       it "redirect keeps headers" caseRedirect
+      it "deals with impure InternalError values" caseImpure
 
 runner :: Session () -> IO ()
 runner f = toWaiApp Y >>= runSession f
@@ -48,3 +58,10 @@
     res <- request defaultRequest { pathInfo = ["redirect"] }
     assertStatus 301 res
     assertHeader "foo" "bar" res
+
+caseImpure :: IO ()
+caseImpure = runner $ do
+    res <- request defaultRequest { pathInfo = ["impure"] }
+    assertStatus 500 res
+    assertBodyContains "impure!" res
+    assertHeader "ERROR" "HANDLER" res
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/yesod-core-1.4.21/yesod-core.cabal 
new/yesod-core-1.4.22/yesod-core.cabal
--- old/yesod-core-1.4.21/yesod-core.cabal      2016-06-20 16:31:06.000000000 
+0200
+++ new/yesod-core-1.4.22/yesod-core.cabal      2016-06-27 09:45:17.000000000 
+0200
@@ -1,5 +1,5 @@
 name:            yesod-core
-version:         1.4.21
+version:         1.4.22
 license:         MIT
 license-file:    LICENSE
 author:          Michael Snoyman <mich...@snoyman.com>
@@ -22,7 +22,7 @@
   README.md
 
 library
-    build-depends:   base                  >= 4.3      && < 5
+    build-depends:   base                  >= 4.6      && < 5
                    , time                  >= 1.1.4
                    , wai                   >= 3.0
                    , wai-extra             >= 3.0.7
@@ -63,7 +63,8 @@
                    , unix-compat
                    , conduit-extra
                    , exceptions            >= 0.6
-                   , deepseq
+                   , deepseq               >= 1.3
+                   , deepseq-generics
                    , mwc-random
                    , primitive
                    , word8


Reply via email to