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 <whissi@gentoo.org> for finding the bug.
most_sig_bit_different )
where
most_sig_bit_different )
where
+import Data.Word (Word32)
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
import Test.Tasty ( TestTree, testGroup )
import Test.Tasty.HUnit ( (@?=), testCase )
-- | 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.
-- | 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.
IPv4Address oct1 oct2 oct3 oct4
where
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)
-- 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.
-- 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
oct1 = toEnum shifted_x1 :: Octet
oct2 = toEnum shifted_x2 :: Octet
oct3 = toEnum shifted_x3 :: Octet
where
desc = "192.168.0.0 in base-10 is 3232235520"
expected = mk_testaddr 192 168 0 0
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
test_ord_instance1 :: TestTree