From 71f626d17e4308e00a894d088626bb72322dedec Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Sun, 3 Mar 2019 10:43:39 -0500 Subject: [PATCH] src/IPv4Address.hs: convert Int to Word32 before doing math in toEnum. On x86, the machine Int type isn't big enough to hold half of the IPv4 address space. Big addresses get converted into negative numbers, and all hell breaks loose. Unfortunately, the Int type is baked into the Enum typeclass, so we can't avoid it entirely. We can however cast to Word32 before doing any math. This commit updates the toEnum implementation for IPv4Address to convert back and forth between Word32, fixing part of this bug. However, the inverse property of toEnum and fromEnum is still failing for IPv4Address. Thanks to Thomas Deutschmann for finding the bug. --- src/IPv4Address.hs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/IPv4Address.hs b/src/IPv4Address.hs index 7e50212..85d8661 100644 --- a/src/IPv4Address.hs +++ b/src/IPv4Address.hs @@ -5,6 +5,7 @@ module IPv4Address( most_sig_bit_different ) where +import Data.Word (Word32) import Test.Tasty ( TestTree, testGroup ) import Test.Tasty.HUnit ( (@?=), testCase ) @@ -188,18 +189,26 @@ instance Enum IPv4Address where -- | Convert an 'Int' @x@ to an 'IPv4Address'. Each octet of @x@ is -- right-shifted by the appropriate number of bits, and the fractional -- part is dropped. - toEnum x = + toEnum y = IPv4Address oct1 oct2 oct3 oct4 where + -- Convert the input Int to a Word32 before we proceed. On x86, + -- the Int that we get could be negative (half of all IP + -- addresses correspond to negative numbers), and then the magic + -- below doesn't work. The Word32 type is unsigned, so we do the + -- math on that and then convert everything back to Int later on + -- once we have four much-smaller non-negative numbers. + x = fromIntegral y :: Word32 + -- Chop off the higher octets. x1 = x `mod` 2^32, would be -- redundant. x2 = x `mod` 2^(24 :: Integer) x3 = x `mod` 2^(16 :: Integer) - x4 = x `mod` 2^(8 :: Integer) + x4 = (fromIntegral $ x `mod` 2^(8 :: Integer)) :: Int -- Perform right-shifts. x4 doesn't need a shift. - shifted_x1 = x `quot` 2^(24 :: Integer) - shifted_x2 = x2 `quot` 2^(16 :: Integer) - shifted_x3 = x3 `quot` 2^(8 :: Integer) + shifted_x1 = (fromIntegral $ x `quot` 2^(24 :: Integer)) :: Int + shifted_x2 = (fromIntegral $ x2 `quot` 2^(16 :: Integer)) :: Int + shifted_x3 = fromIntegral $ x3 `quot` 2^(8 :: Integer) :: Int oct1 = toEnum shifted_x1 :: Octet oct2 = toEnum shifted_x2 :: Octet oct3 = toEnum shifted_x3 :: Octet @@ -432,7 +441,11 @@ test_to_enum = where desc = "192.168.0.0 in base-10 is 3232235520" expected = mk_testaddr 192 168 0 0 - actual = toEnum 3232235520 :: IPv4Address + -- We declare the big number as Word32 because otherwise, on x86, + -- we get a warning that it's too big to fit in a 32-bit integer. + -- Ultimately we convert it to a (negative) Int on those systems + -- anyway, but the gymnastics declare our intent to the compiler. + actual = toEnum (fromIntegral (3232235520 :: Word32)) :: IPv4Address test_ord_instance1 :: TestTree -- 2.43.2