2012-09-11 14 views
9

Tengo un corredor de prueba simple para el bug que está en mi módulo OpenPGP https://github.com/singpolyma/OpenPGP-Haskell/blob/master/Data/OpenPGP.hs:¿Por qué este código se comporta de manera diferente con optomisations encendido o apagado?

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 

Si compila esto (en GHC 7.4.1) con:

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

Funciona como se esperaba (es decir, se imprime True), pero si se compila como esto:

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

o esto:

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

Se imprimirá False.

No estoy utilizando ninguna extensión (excepto un uso trivial de CPP) o llamadas de bajo nivel o inseguras, y el comportamiento debería ser de mi biblioteca y no una dependencia, ya que es solo mi código el que se recompila aquí .

+5

Puedo reproducir este error en GHC 7.4.2 –

+1

¿Estás usando binario o cereal cuando observas este error? –

Respuesta

5

Es un error en su código. Considere

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

y

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) 

Ahora, si codificamos MPI 0, bytes' está vacía, por lo tanto bytes = B.singleton 0 y por lo tanto es 0. bytes `B.index` 0

Pero es logBase 2 0-Infinity, y sólo es bien floor definido para valores finitos (dentro del rango del tipo de destino).

Al compilar sin optimizaciones, floor usa el patrón de bits a través de decodeFloat. Entonces floor (logBase 2 0) produce 0 para todos los tipos enteros de ancho fijo estándar.

Con optimizaciones, una regla de reescritura está activa y floor usa el primop double2Int#, que devuelve lo que haga el hardware, en x86 resp. x86-64, eso es minBound :: Int, hasta donde yo sé, independientemente del patrón de bits.El código en cuestión es

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

y, por supuesto, -Infinity < int2Double minBound, por lo que el valor se convierte en minBound - 1, que por lo general es maxBound.

Por supuesto que causa un mal resultado, ya que ahora la "longitud" es put para MPI 0 es 0 y el 0 bytes poner después del campo "longitud" se interpreta como parte de la "longitud" de la próxima MPI .

+0

Gracias! No hubiera esperado que el comportamiento de 'floor' cambiara con' -O', pero tienes razón en que tenía un error en mis suposiciones de todos modos. – singpolyma

+1

Hay algunos lugares donde las reglas de reescritura cambian el comportamiento. Principalmente cuando no hay un resultado correcto de todos modos, como con los valores fuera de rango para 'floor' et al. Pero a veces incluso en lugares con resultados significativos, p. '(realToFrac :: Float -> Double) (0/0)' produce '-5.104235503814077e38' sin optimizaciones,' NaN' con optimizaciones. El informe de idioma dice 'realToFrac = fromRational. toRational', que produce el primero. Como 'Rational' no puede manejar' NaN's e infinities, no hay una buena forma de tratarlos en esa conversión y son golpeados. El primop los conserva. –

+0

Las cosas siempre son mucho más divertidas cuando se trata de NaN ... –

5

El problema está relacionado con su instancia BINARY_CLASS para MPI. Si cambio

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

veo la salida (compilado con 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)}) 

Cambio de la instancia de MPI a esta implementación más sencilla:

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) 

corrige el problema.

Hay algunas cosas que podrían ser la fuente del problema. Es posible que su código sea correcto (no lo he comprobado de una forma u otra), en cuyo caso GHC está realizando alguna transformación no válida que conduce a un desbordamiento/subdesbordamiento en alguna parte. También es posible que su código esté haciendo algo incorrecto que solo está expuesto por ciertas optimizaciones.

Cuestiones relacionadas