我有一个简单的测试运行器来处理我的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)或低级或不安全的调用,并且行为应该来自我的库而不是依赖项,因为它只是我的代码在这里重新编译。
答案 0 :(得分:5)
问题与MPI
的BINARY_CLASS实例有关。如果我改变
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正在执行某些无效转换,导致某处出现溢出/下溢。您的代码也可能正在执行某些不正确的操作,只能通过某些优化来实现。
答案 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 0
进行编码,bytes'
为空,则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上的硬件所做的任何操作。 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
。
当然导致错误的结果,因为现在put
的{{1}}的“长度”变为0,而“长度”字段之后的0字节被解释为下一个MPI 0
的“长度”。