Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid using Opt.auto to avoid overflows going silent #864

Merged
merged 6 commits into from
Aug 27, 2024

Conversation

smelc
Copy link
Contributor

@smelc smelc commented Aug 8, 2024

Changelog

- description: |
    Forbid incorrect values in parsers of many Int-like options. Previously those values would overflow and be turned into a random valid value. If this breaks your use case, this means your use case wasn't doing what you expected.
# uncomment types applicable to the change:
  type:
  # - feature        # introduces a new feature
  # - breaking       # the API has changed in a breaking way
  # - compatible     # the API has changed but is non-breaking
  # - optimisation   # measurable performance improvements
  # - refactoring    # QoL changes
  - bugfix         # fixes a defect
  # - test           # fixes/modifies tests
  # - maintenance    # not directly related to the code
  # - release        # related to a new release preparation
  # - documentation  # change in code docs, haddocks...

Context

We use auto a lot in parsing. auto relies under the hood on Read instances which are known to have problems. Some instances accept wrong values and return out of thin air values. This PR fixes this.

Fixes #860

How to trust this PR

  • Try this PR with cardano-testnet: tested cabal test cardano-testnet-test with cardano-node at e9ab511272a7694196778007211991a9984557e8 and cardano-cli at 44ca6ed
  • Run git grep auto "*.hs" and observe that all the remaining calls are in a byron file (I didn't change those)

Checklist

  • Commit sequence broadly makes sense and commits have useful messages
  • New tests are added if needed and existing tests are updated. See Running tests for more details
  • Self-reviewed the diff

Copy link
Contributor

@carbolymer carbolymer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the direction, but wordReader needs some polishing. 😃

@smelc
Copy link
Contributor Author

smelc commented Aug 9, 2024

@carbolymer> I did the changes 👍

Given your last comment, I assume you're fine with me deploying this to many more code locations? (please don't approve though yet, because I'll extend this PR).

@smelc smelc force-pushed the smelc/fix-auto-parsing branch 2 times, most recently from dda38ff to 62973d5 Compare August 12, 2024 09:58
@smelc smelc force-pushed the smelc/fix-auto-parsing branch 2 times, most recently from 105c015 to d3abc03 Compare August 19, 2024 10:01
Copy link
Contributor

@Jimbo4350 Jimbo4350 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Now just replace all calls to auto.

cardano-cli/src/Cardano/CLI/EraBased/Options/Common.hs Outdated Show resolved Hide resolved
@smelc smelc force-pushed the smelc/fix-auto-parsing branch 3 times, most recently from 8317ab1 to 13f04eb Compare August 22, 2024 15:23
@smelc smelc marked this pull request as ready for review August 22, 2024 15:24
@smelc smelc force-pushed the smelc/fix-auto-parsing branch 2 times, most recently from 49fc27e to 44ca6ed Compare August 22, 2024 15:30
pairIntegralReader :: (Typeable a, Integral a, Bits a) => ReadM (a, a)
pairIntegralReader = readerFromParsecParser pairIntegralParsecParser

pairIntegralParsecParser :: (Typeable a, Integral a, Bits a) => Parsec.Parser (a, a)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is important to review

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks fine to me 👌🏻

-- @cabal test cardano-cli-test --test-options '-p "/pair integral reader/"'@
hprop_pair_integral_reader :: Property
hprop_pair_integral_reader = propertyOnce $ do
assert $ isRight $ parse @Word "(0, 0)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can improve these tests with hedgehog generators. Example:

diff --git a/cardano-cli/test/cardano-cli-test/Test/Cli/Parser.hs b/cardano-cli/test/cardano-cli-test/Test/Cli/Parser.hs
index 1bf19f8b8..c72c8a093 100644
--- a/cardano-cli/test/cardano-cli-test/Test/Cli/Parser.hs
+++ b/cardano-cli/test/cardano-cli-test/Test/Cli/Parser.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
 
 module Test.Cli.Parser
@@ -12,11 +14,14 @@ import           Cardano.CLI.EraBased.Options.Common (integralParsecParser,
 import           Data.Bits (Bits)
 import           Data.Data (Typeable)
 import           Data.Either (isLeft, isRight)
+import           Data.Proxy
 import           Data.Word (Word16)
 import qualified Text.Parsec as Parsec
 
-import           Hedgehog (Property, assert)
+import           Hedgehog
 import           Hedgehog.Extras (propertyOnce)
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
 
 -- | Execute me with:
 -- @cabal test cardano-cli-test --test-options '-p "/integral reader/"'@
@@ -45,9 +50,8 @@ hprop_integral_reader = propertyOnce $ do
 -- @cabal test cardano-cli-test --test-options '-p "/pair integral reader/"'@
 hprop_pair_integral_reader :: Property
 hprop_pair_integral_reader = propertyOnce $ do
-  assert $ isRight $ parse @Word "(0, 0)"
-  assert $ isRight $ parse @Word "   (   0  ,   0   )"
-  assert $ isRight $ parse @Word " (18446744073709551615   ,    18446744073709551614   )   "
+  validArbitraryTuple <- forAll $ genNumberTuple (Proxy :: Proxy Word)
+  assert $ isRight $ parse @Word validArbitraryTuple
 
   assert $ isLeft $ parse @Word "(0, 0, 0)"
   assert $ isLeft $ parse @Word "(-1, 0)"
@@ -64,3 +68,18 @@ hprop_pair_integral_reader = propertyOnce $ do
     case Parsec.runParser pairIntegralParsecParser () "" s of
       Left parsecError -> Left $ show parsecError
       Right x -> Right x
+
+genNumberTuple
+  :: forall integral
+   . Integral integral
+  => Show integral
+  => Proxy integral -> Gen String
+genNumberTuple _ = do
+  x :: integral <- Gen.integral (Range.linear 0 100)
+  y :: integral <- Gen.integral (Range.linear 0 100)
+  space1 <- genArbitrarySpace
+  space2 <- genArbitrarySpace
+  return $ "(" ++ space2 ++ show x ++ space1 ++ "," ++ space2 ++ show y ++ space1 ++ ")"
+
+genArbitrarySpace :: Gen String
+genArbitrarySpace = Gen.string (Range.linear 0 5) (return ' ')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should as much as possible avoid unit tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took your suggestion and split the function into two, since the positive tests need to be run with property, while the negative tests need to run with propertyOnce 👍

Copy link
Contributor

@Jimbo4350 Jimbo4350 Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while the negative tests need to run with propertyOnce

Why? You could also write a generator to inject a random character (or multiple characters) between the brackets for the negative tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but I would need to rule out generators that would still yield a valid value (e.g. if the generated character is a number) and I believe writing a good generator for negative tests it overkill for testing this parser. So I'll stick to the manual negative tests to speed up merging.

Copy link
Contributor

@Jimbo4350 Jimbo4350 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM however you can improve your tests further using hedgehog generators.

-- @cabal test cardano-cli-test --test-options '-p "/integral reader/"'@
hprop_integral_reader :: Property
hprop_integral_reader = propertyOnce $ do
assert $ isRight $ parse @Word "0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Done with:

  w <- forAll $ Gen.word $ Gen.linear minBound maxBound
  assert $ isRight $ parse @Word (show w)

validArbitraryTuple <- forAll $ genNumberTuple (Proxy :: Proxy Word)
assert $ isRight $ parse @Word validArbitraryTuple

assert $ isLeft $ parse @Word "(0, 0, 0)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write a generator that injects a random character (or characters) into the string generated by genNumberTuple. This would provide better coverage.

assert $ isLeft $ parse @Word "(0, 0, 0)"
assert $ isLeft $ parse @Word "(-1, 0)"
assert $ isLeft $ parse @Word "(18446744073709551616, 0)"
assert $ isLeft $ parse @Word "(0, 18446744073709551616)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are already tested in hprop_integral_pair_reader_negative

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops, good catch. Correcting 👍

hprop_integral_reader :: Property
hprop_integral_reader = property $ do
assert $ isRight $ parse @Word "0"
assert $ isRight $ parse @Word "42"
Copy link
Contributor

@carbolymer carbolymer Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be better to check the exact value in case of Rights here

Suggested change
assert $ isRight $ parse @Word "42"
parse @Word "42" === Right 42

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, changing 👍

assert $ isRight $ parse @Word "0"
assert $ isRight $ parse @Word "42"
assert $ isLeft $ parse @Word "-1"
assert $ isLeft $ parse @Word "18446744073709551616"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to write this as

Suggested change
assert $ isLeft $ parse @Word "18446744073709551616"
assertWith isLeft $ parse @Word "18446744073709551616"

assert does not report the value on assertion failure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to (for example) assertWith (parse @Word "18446744073709551616") isLeft

Copy link
Contributor

@carbolymer carbolymer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smelc smelc enabled auto-merge August 27, 2024 15:02
@smelc smelc added this pull request to the merge queue Aug 27, 2024
Merged via the queue into main with commit 9586517 Aug 27, 2024
25 checks passed
@smelc smelc deleted the smelc/fix-auto-parsing branch August 27, 2024 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Negative value overflows into positive in create-protocol-parameters-update
4 participants