2012-09-11 6 views
9

Ich habe einen einfachen Test Läufer für den Fehler, der in meinem OpenPGP-Modul ist https://github.com/singpolyma/OpenPGP-Haskell/blob/master/Data/OpenPGP.hs:Warum verhält sich dieser Code bei aktivierten oder deaktivierten Optionen anders?

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 

Wenn Sie diese kompilieren (auf ghc 7.4.1) mit:

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

Es funktioniert wie erwartet (das heißt, er druckt True), aber wenn Sie wie folgt zusammenstellen:

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

oder dies:

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

Es wird False gedruckt.

Ich verwende keine Erweiterungen (außer einer trivialen Verwendung von CPP) oder Low-Level oder unsichere Aufrufe, und das Verhalten sollte von meiner Bibliothek und keine Abhängigkeit sein, da nur mein Code hier neu kompiliert wird .

+5

Ich kann diesen Fehler in GHC 7.4.2 reproduzieren –

+1

Verwenden Sie binär oder Müsli, wenn Sie diesen Fehler beobachten? –

Antwort

5

Es ist ein Fehler in Ihrem Code. Betrachten

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

und

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) 

Wenn wir nun MPI 0 kodieren, bytes' ist leer, so bytes = B.singleton 0 und damit bytes `B.index` 0 ist 0.

Aber logBase 2 0 ist -Infinity und floor ist nur gut Definiert für endliche Werte (innerhalb des Bereichs des Zieltyps).

Beim Übersetzen ohne Optimierungen verwendet floor das Bitmuster über decodeFloat. Dann liefert floor (logBase 2 0) 0 für alle standardmäßigen Integer-Typen mit fester Breite.

Bei Optimierungen ist eine Rewrite-Regel aktiv und floor verwendet den Primop double2Int#, der unabhängig von der Hardware auf x86 resp. x86-64, das ist minBound :: Int, soweit ich weiß, unabhängig vom Bitmuster.Der entsprechende Code ist

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

und natürlich -Infinity < int2Double minBound, so dass der Wert wird minBound - 1, die in der Regel maxBound ist.

Natürlich ist das ein falsches Ergebnis verursacht, da jetzt die „Länge“, die put für MPI 0 ist 0 wird, und das 0 Byte nach dem Feld „Länge“ gesetzt werden als Teil der „Länge“ der nächsten MPI interpretiert .

+0

Danke! Ich hätte nicht erwartet, dass sich das Verhalten von "floor" mit "-O" ändert, aber du hast recht, dass ich sowieso einen Fehler in meinen Annahmen hatte. – singpolyma

+1

Es gibt einige Stellen, an denen Umschreibungsregeln das Verhalten ändern. Meistens, wenn es sowieso kein korrektes Ergebnis gibt, wie bei Werten außerhalb des Bereichs für "floor" et al. Aber manchmal sogar an Stellen mit bedeutungsvollen Ergebnissen, z.B. '(realToFrac :: Float -> Double) (0/0)' erzeugt '-5.104235503814077e38' ohne Optimierungen,' NaN' mit Optimierungen. Der Sprachbericht sagt 'realToFrac = fromRational. toRational', der den ersten produziert. Da "Rational" nicht wirklich mit NaNs und Unendlichkeiten umgehen kann, gibt es keinen guten Weg, sie in dieser Konvertierung zu behandeln, und sie sind zerschlagen. Der Primop bewahrt sie. –

+0

Die Dinge machen immer viel mehr Spaß, wenn NaN involviert ist ... –

5

Das Problem hängt mit Ihrer BINARY_CLASS-Instanz für MPI zusammen. Wenn ich

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

ändern sehe ich die Ausgabe (kompilierte mit -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)}) 

Ändern der MPI-Instanz auf diese einfachen Implementierung:

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) 

behebt das Problem.

Es gibt ein paar Dinge, die die Problemquelle sein könnten. Es ist möglich, dass Ihr Code korrekt ist (ich habe dies nicht in der einen oder anderen Weise überprüft). In diesem Fall führt GHC eine ungültige Transformation durch, die irgendwo zu einem Überlauf/Unterlauf führt. Es ist auch möglich, dass Ihr Code etwas Falsches tut, das nur bei bestimmten Optimierungen verfügbar ist.

Verwandte Themen