9

我有一个简单的测试运行程序,用于我的 OpenPGP 模块https://github.com/singpolyma/OpenPGP-Haskell/blob/master/Data/OpenPGP.hs中的错误:

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 的微不足道的使用)或低级或不安全的调用,并且行为应该来自我的库而不是依赖项,因为只有我的代码在这里重新编译。

4

2 回答 2

5

问题与您的 BINARY_CLASS 实例有关MPI。如果我改变

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

我看到了输出(用 -O2 编译)

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 正在执行一些无效的转换,导致某处出现溢出/下溢。也有可能您的代码正在做一些不正确的事情,只有某些优化才会暴露出来。

于 2012-09-11T05:42:28.957 回答
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 0is-Infinityfloor仅对有限值(在目标类型的范围内)定义明确。

在没有优化的情况下编译时,floor使用位模式 via 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

当然这会导致错误的结果,因为现在putfor的“长度”MPI 0变为 0,并且放在“长度”字段之后的 0 字节被解释为 next 的“长度”的一部分MPI

于 2012-09-11T13:24:07.590 回答