]> gitweb.michael.orlitzky.com - hath.git/commitdiff
src/IPv4Address.hs: convert Int to Word32 before doing math in toEnum.
authorMichael Orlitzky <michael@orlitzky.com>
Sun, 3 Mar 2019 15:43:39 +0000 (10:43 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Sun, 3 Mar 2019 15:43:39 +0000 (10:43 -0500)
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.

src/IPv4Address.hs

index 7e502125722cbfb354a53d517acd206a3fe678ad..85d86616023b19f3cc936ac2f69f1b65020df880 100644 (file)
@@ -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