2012-09-11 64 views
9

我有這是我的OpenPGP模塊https://github.com/singpolyma/OpenPGP-Haskell/blob/master/Data/OpenPGP.hs的bug一個簡單的測試運行:爲什麼此代碼在開啓或關閉optomisations時行爲不同?

module Main where 

import Data.OpenPGP 
import Data.Binary (encode, decode) 

packet = EmbeddedSignaturePacket (signaturePacket 2 168 ECDSA SHA256 [] [SignatureCreationTimePacket 1013401916,IssuerPacket "36FE856F4219F1C7"] 48065 [MPI 4,MPI 11,MPI 60,MPI 69,MPI 37,MPI 33,MPI 18,MPI 72,MPI 41,MPI 36,MPI 43,MPI 41,MPI 53,MPI 9,MPI 53,MPI 35,MPI 3,MPI 40,MPI 14,MPI 79,MPI 1,MPI 4,MPI 51,MPI 23,MPI 62,MPI 62,MPI 62,MPI 7,MPI 68,MPI 51,MPI 13,MPI 49,MPI 8,MPI 64,MPI 32,MPI 50,MPI 59,MPI 17,MPI 43,MPI 12,MPI 67,MPI 5,MPI 67,MPI 5,MPI 25,MPI 63,MPI 0,MPI 53,MPI 2,MPI 36,MPI 83,MPI 39,MPI 54,MPI 65,MPI 54,MPI 35,MPI 62,MPI 63,MPI 26,MPI 4,MPI 82,MPI 57,MPI 85,MPI 71,MPI 43,MPI 77]) 

main = print $ decode (encode packet) == packet 

如果您編譯這個(GHC上7.4.1)有:

ghc -O0 -fforce-recomp --make t.hs 

它將按預期工作(也就是說,它打印True),但如果你這樣進行編譯:

ghc -O1 -fforce-recomp --make t.hs 

或本:

ghc -O2 -fforce-recomp --make t.hs 

這將打印False

我沒有使用任何擴展(CPP的普通用法除外)或低級別或不安全的調用,並且行爲應該來自我的庫而不是依賴項,因爲它只是我的代碼在此處重新編譯。

+5

我可以在GHC 7.4.2中重現此錯誤 –

+1

當您觀察此錯誤時,您是否使用二進制或穀類? –

回答

5

它在你的代碼中的錯誤。考慮

MPI 63,MPI 0,MPI 53 
     ^^^^^ 

instance BINARY_CLASS MPI where 
    put (MPI i) = do 
     put (((fromIntegral . B.length $ bytes) - 1) * 8 
       + floor (logBase (2::Double) $ fromIntegral (bytes `B.index` 0)) 
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
       + 1 :: Word16) 
    putSomeByteString bytes 
    where 
    bytes = if B.null bytes' then B.singleton 0 else bytes' 
    bytes' = B.reverse $ B.unfoldr (\x -> 
        if x == 0 then Nothing else 
          Just (fromIntegral x, x `shiftR` 8) 
      ) (assertProp (>=0) i) 

現在,如果我們編碼MPI 0bytes'是空的,因此bytes = B.singleton 0因此bytes `B.index` 0爲0

logBase 2 0-Infinity,和floor只有良好定義爲有限值(在目標類型的範圍內)。

編譯時沒有進行優化,floor使用通過decodeFloat的位模式。然後floor (logBase 2 0)對於所有標準固定寬度整數類型產生0。

通過優化,重寫規則處於活動狀態,並且floor使用primop double2Int#,它在x86和resp上返回硬件所做的任何操作。 X86-64,這是minBound :: Int,據我所知,無論是位模式的。相關的代碼是

floorDoubleInt :: Double -> Int 
floorDoubleInt (D# x) = 
    case double2Int# x of 
     n | x <## int2Double# n -> I# (n -# 1#) 
     | otherwise    -> I# n 

當然,-Infinity < int2Double minBound的和,所以該值變爲minBound - 1,這通常是maxBound

當然,這會導致錯誤的結果,因爲現在的「長度」,也就是putMPI 0變爲0,和0字節的「長度」字段之後把被解釋爲下一MPI的「長度」的一部分。

+0

謝謝!我不希望'floor'的行爲會隨着'-O'而改變,但是你說得對,我的假設中有一個錯誤。 – singpolyma

+1

有幾個地方的重寫規則會改變行爲。大多數情況下,無論如何沒有正確的結果,比如「floor」等超出範圍的值。但有時甚至在具有有意義結果的地方,例如'(realToFrac :: Float - > Double)(0/0)'產生'-5.104235503814077e38'沒有優化,'NaN'優化。語言報告稱「realToFrac = fromRational」。 toRational',它產生第一個。由於'Rational'不能真正處理'NaN'和無窮大,所以沒有什麼好的辦法在轉換中對待它們,並且它們被破壞了。 primop保留它們。 –

+0

當涉及NaN時,事情總是非常有趣... –

5

問題是關係到你的BINARY_CLASS實例MPI。如果我改變

main = do 
    print packet 
    print (decode (encode packet) :: SignatureSubpacket) 
    print $ decode (encode packet) == packet 

我看到的輸出(帶有-02編譯)

EmbeddedSignaturePacket (SignaturePacket {version = 2, signature_type = 168, key_algorithm = ECDSA, hash_algorithm = SHA256, hashed_subpackets = [], unhashed_subpackets = [SignatureCreationTimePacket 1013401916,IssuerPacket "36FE856F4219F1C7"], hash_head = 48065, signature = [MPI 4,MPI 11,MPI 60,MPI 69,MPI 37,MPI 33,MPI 18,MPI 72,MPI 41,MPI 36,MPI 43,MPI 41,MPI 53,MPI 9,MPI 53,MPI 35,MPI 3,MPI 40,MPI 14,MPI 79,MPI 1,MPI 4,MPI 51,MPI 23,MPI 62,MPI 62,MPI 62,MPI 7,MPI 68,MPI 51,MPI 13,MPI 49,MPI 8,MPI 64,MPI 32,MPI 50,MPI 59,MPI 17,MPI 43,MPI 12,MPI 67,MPI 5,MPI 67,MPI 5,MPI 25,MPI 63,MPI 0,MPI 53,MPI 2,MPI 36,MPI 83,MPI 39,MPI 54,MPI 65,MPI 54,MPI 35,MPI 62,MPI 63,MPI 26,MPI 4,MPI 82,MPI 57,MPI 85,MPI 71,MPI 43,MPI 77], trailer = Chunk "\168" (Chunk "<gI<" Empty)}) 
EmbeddedSignaturePacket (SignaturePacket {version = 2, signature_type = 168, key_algorithm = ECDSA, hash_algorithm = SHA256, hashed_subpackets = [], unhashed_subpackets = [SignatureCreationTimePacket 1013401916,IssuerPacket "36FE856F4219F1C7"], hash_head = 48065, signature = [MPI 4,MPI 11,MPI 60,MPI 69,MPI 37,MPI 33,MPI 18,MPI 72,MPI 41,MPI 36,MPI 43,MPI 41,MPI 53,MPI 9,MPI 53,MPI 35,MPI 3,MPI 40,MPI 14,MPI 79,MPI 1,MPI 4,MPI 51,MPI 23,MPI 62,MPI 62,MPI 62,MPI 7,MPI 68,MPI 51,MPI 13,MPI 49,MPI 8,MPI 64,MPI 32,MPI 50,MPI 59,MPI 17,MPI 43,MPI 12,MPI 67,MPI 5,MPI 67,MPI 5,MPI 25,MPI 63,MPI 0,MPI 0,MPI 339782829898145924110968965855122255180100961470274369007196973863828909184332476115285611703086303618816635857833592912611149], trailer = Chunk "\168" (Chunk "<gI<" Empty)}) 

更改您的MPI實例這種更簡單的實現:

newtype MPI = MPI Integer deriving (Show, Read, Eq, Ord) 
instance BINARY_CLASS MPI where 
    put (MPI i) = do 
    put (fromIntegral $ B.length bytes :: Word16) 
    putSomeByteString bytes 
    where 
    bytes = if B.null bytes' then B.singleton 0 else bytes' 
    bytes' = B.pack . map (read . (:[])) $ show i 
    get = do 
    length <- fmap fromIntegral (get :: Get Word16) 
    bytes <- getSomeByteString length 
    return (MPI $ read $ concatMap show $ B.unpack bytes) 

解決了這個問題。

有幾件事情,可能是問題的根源。這有可能是你的代碼是正確的(我沒有檢查這一項或其他方式),在這種情況下GHC正在執行一些無效的轉型導致的溢/下溢的地方。也有可能你的代碼做了一些不正確的事情,而這些事情只能通過某些優化來暴露出來。