From 02d3304782c715cb787e53a20f4e44ec249d3466 Mon Sep 17 00:00:00 2001 From: Mark Jansen Date: Thu, 5 Sep 2024 23:06:32 +0200 Subject: [PATCH] Fix bug in TokenStream parser When presented with just '-' it would access the string at position [1] --- src/catch2/internal/catch_clara.cpp | 2 +- .../Baselines/automake.sw.approved.txt | 1 + .../Baselines/automake.sw.multi.approved.txt | 1 + .../Baselines/compact.sw.approved.txt | 10 +++- .../Baselines/compact.sw.multi.approved.txt | 10 +++- .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 40 +++++++++++++- .../Baselines/console.sw.multi.approved.txt | 40 +++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 3 +- .../Baselines/junit.sw.multi.approved.txt | 3 +- .../Baselines/sonarqube.sw.approved.txt | 1 + .../Baselines/sonarqube.sw.multi.approved.txt | 1 + tests/SelfTest/Baselines/tap.sw.approved.txt | 14 ++++- .../Baselines/tap.sw.multi.approved.txt | 14 ++++- .../Baselines/teamcity.sw.approved.txt | 2 + .../Baselines/teamcity.sw.multi.approved.txt | 2 + tests/SelfTest/Baselines/xml.sw.approved.txt | 55 ++++++++++++++++++- .../Baselines/xml.sw.multi.approved.txt | 55 ++++++++++++++++++- .../IntrospectiveTests/Clara.tests.cpp | 15 +++++ 19 files changed, 254 insertions(+), 19 deletions(-) diff --git a/src/catch2/internal/catch_clara.cpp b/src/catch2/internal/catch_clara.cpp index c76089ee..f9dd9138 100644 --- a/src/catch2/internal/catch_clara.cpp +++ b/src/catch2/internal/catch_clara.cpp @@ -76,7 +76,7 @@ namespace Catch { { TokenType::Argument, next.substr( delimiterPos + 1, next.size() ) } ); } else { - if ( next[1] != '-' && next.size() > 2 ) { + if ( next.size() > 1 && next[1] != '-' && next.size() > 2 ) { // Combined short args, e.g. "-ab" for "-a -b" for ( size_t i = 1; i < next.size(); ++i ) { m_tokenBuffer.push_back( diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index ce9475b8..08fbf09c 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -105,6 +105,7 @@ Nor would this :test-result: PASS CaseInsensitiveEqualsTo is case insensitive :test-result: PASS CaseInsensitiveLess is case insensitive :test-result: PASS Character pretty printing +:test-result: PASS Clara::Arg does not crash on incomplete input :test-result: PASS Clara::Arg supports single-arg parse the way Opt does :test-result: PASS Clara::Opt supports accept-many lambdas :test-result: PASS ColourGuard behaviour diff --git a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt index 06f2f58d..c4b7b967 100644 --- a/tests/SelfTest/Baselines/automake.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.multi.approved.txt @@ -103,6 +103,7 @@ :test-result: PASS CaseInsensitiveEqualsTo is case insensitive :test-result: PASS CaseInsensitiveLess is case insensitive :test-result: PASS Character pretty printing +:test-result: PASS Clara::Arg does not crash on incomplete input :test-result: PASS Clara::Arg supports single-arg parse the way Opt does :test-result: PASS Clara::Opt supports accept-many lambdas :test-result: PASS ColourGuard behaviour diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index 0f107e71..1f0dd012 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -389,6 +389,12 @@ ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( '\0 ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( static_cast(2) ) == "2" for: "2" == "2" ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( static_cast(5) ) == "5" for: "5" == "5" Clara.tests.cpp:: passed: name.empty() for: true +Clara.tests.cpp:: passed: result for: {?} +Clara.tests.cpp:: passed: result.type() == Catch::Clara::Detail::ResultType::Ok for: 0 == 0 +Clara.tests.cpp:: passed: parsed.type() == Catch::Clara::ParseResultType::NoMatch for: 1 == 1 +Clara.tests.cpp:: passed: parsed.remainingTokens().count() == 2 for: 2 == 2 +Clara.tests.cpp:: passed: name.empty() for: true +Clara.tests.cpp:: passed: name.empty() for: true Clara.tests.cpp:: passed: name == "foo" for: "foo" == "foo" Clara.tests.cpp:: passed: !(parse_result) for: !{?} Clara.tests.cpp:: passed: parse_result for: {?} @@ -2844,7 +2850,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 418 | 312 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2259 | 2077 passed | 147 failed | 35 failed as expected +test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected +assertions: 2265 | 2083 passed | 147 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 4b9d35c6..77812a62 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -387,6 +387,12 @@ ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( '\0 ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( static_cast(2) ) == "2" for: "2" == "2" ToStringGeneral.tests.cpp:: passed: ::Catch::Detail::stringify( static_cast(5) ) == "5" for: "5" == "5" Clara.tests.cpp:: passed: name.empty() for: true +Clara.tests.cpp:: passed: result for: {?} +Clara.tests.cpp:: passed: result.type() == Catch::Clara::Detail::ResultType::Ok for: 0 == 0 +Clara.tests.cpp:: passed: parsed.type() == Catch::Clara::ParseResultType::NoMatch for: 1 == 1 +Clara.tests.cpp:: passed: parsed.remainingTokens().count() == 2 for: 2 == 2 +Clara.tests.cpp:: passed: name.empty() for: true +Clara.tests.cpp:: passed: name.empty() for: true Clara.tests.cpp:: passed: name == "foo" for: "foo" == "foo" Clara.tests.cpp:: passed: !(parse_result) for: !{?} Clara.tests.cpp:: passed: parse_result for: {?} @@ -2833,7 +2839,7 @@ InternalBenchmark.tests.cpp:: passed: med == 18. for: 18.0 == 18.0 InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -test cases: 418 | 312 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2259 | 2077 passed | 147 failed | 35 failed as expected +test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected +assertions: 2265 | 2083 passed | 147 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index f3196855..1003e549 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1610,6 +1610,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 418 | 326 passed | 71 failed | 7 skipped | 14 failed as expected -assertions: 2242 | 2077 passed | 130 failed | 35 failed as expected +test cases: 419 | 327 passed | 71 failed | 7 skipped | 14 failed as expected +assertions: 2248 | 2083 passed | 130 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index ba18ad1f..3519b771 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -3039,6 +3039,42 @@ ToStringGeneral.tests.cpp:: PASSED: with expansion: "5" == "5" +------------------------------------------------------------------------------- +Clara::Arg does not crash on incomplete input +------------------------------------------------------------------------------- +Clara.tests.cpp: +............................................................................... + +Clara.tests.cpp:: PASSED: + CHECK( name.empty() ) +with expansion: + true + +Clara.tests.cpp:: PASSED: + CHECK( result ) +with expansion: + {?} + +Clara.tests.cpp:: PASSED: + CHECK( result.type() == Catch::Clara::Detail::ResultType::Ok ) +with expansion: + 0 == 0 + +Clara.tests.cpp:: PASSED: + CHECK( parsed.type() == Catch::Clara::ParseResultType::NoMatch ) +with expansion: + 1 == 1 + +Clara.tests.cpp:: PASSED: + CHECK( parsed.remainingTokens().count() == 2 ) +with expansion: + 2 == 2 + +Clara.tests.cpp:: PASSED: + CHECK( name.empty() ) +with expansion: + true + ------------------------------------------------------------------------------- Clara::Arg supports single-arg parse the way Opt does ------------------------------------------------------------------------------- @@ -18942,6 +18978,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 418 | 312 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2259 | 2077 passed | 147 failed | 35 failed as expected +test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected +assertions: 2265 | 2083 passed | 147 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index bdb54d43..1fe0b73e 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -3037,6 +3037,42 @@ ToStringGeneral.tests.cpp:: PASSED: with expansion: "5" == "5" +------------------------------------------------------------------------------- +Clara::Arg does not crash on incomplete input +------------------------------------------------------------------------------- +Clara.tests.cpp: +............................................................................... + +Clara.tests.cpp:: PASSED: + CHECK( name.empty() ) +with expansion: + true + +Clara.tests.cpp:: PASSED: + CHECK( result ) +with expansion: + {?} + +Clara.tests.cpp:: PASSED: + CHECK( result.type() == Catch::Clara::Detail::ResultType::Ok ) +with expansion: + 0 == 0 + +Clara.tests.cpp:: PASSED: + CHECK( parsed.type() == Catch::Clara::ParseResultType::NoMatch ) +with expansion: + 1 == 1 + +Clara.tests.cpp:: PASSED: + CHECK( parsed.remainingTokens().count() == 2 ) +with expansion: + 2 == 2 + +Clara.tests.cpp:: PASSED: + CHECK( name.empty() ) +with expansion: + true + ------------------------------------------------------------------------------- Clara::Arg supports single-arg parse the way Opt does ------------------------------------------------------------------------------- @@ -18931,6 +18967,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 418 | 312 passed | 86 failed | 6 skipped | 14 failed as expected -assertions: 2259 | 2077 passed | 147 failed | 35 failed as expected +test cases: 419 | 313 passed | 86 failed | 6 skipped | 14 failed as expected +assertions: 2265 | 2083 passed | 147 failed | 35 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 710169c4..33f207c5 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -420,6 +420,7 @@ at Exception.tests.cpp: + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 80817478..876a4db4 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -419,6 +419,7 @@ at Exception.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index 1cd2858b..18397240 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -13,6 +13,7 @@ at AssertionHandler.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index 02f4bc89..77348788 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -12,6 +12,7 @@ at AssertionHandler.tests.cpp: + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 36da9a8e..ea53d421 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -724,6 +724,18 @@ ok {test-number} - ::Catch::Detail::stringify( '\0' ) == "0" for: "0" == "0" ok {test-number} - ::Catch::Detail::stringify( static_cast(2) ) == "2" for: "2" == "2" # Character pretty printing ok {test-number} - ::Catch::Detail::stringify( static_cast(5) ) == "5" for: "5" == "5" +# Clara::Arg does not crash on incomplete input +ok {test-number} - name.empty() for: true +# Clara::Arg does not crash on incomplete input +ok {test-number} - result for: {?} +# Clara::Arg does not crash on incomplete input +ok {test-number} - result.type() == Catch::Clara::Detail::ResultType::Ok for: 0 == 0 +# Clara::Arg does not crash on incomplete input +ok {test-number} - parsed.type() == Catch::Clara::ParseResultType::NoMatch for: 1 == 1 +# Clara::Arg does not crash on incomplete input +ok {test-number} - parsed.remainingTokens().count() == 2 for: 2 == 2 +# Clara::Arg does not crash on incomplete input +ok {test-number} - name.empty() for: true # Clara::Arg supports single-arg parse the way Opt does ok {test-number} - name.empty() for: true # Clara::Arg supports single-arg parse the way Opt does @@ -4547,5 +4559,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2271 +1..2277 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index 64828cb1..3f8f72eb 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -722,6 +722,18 @@ ok {test-number} - ::Catch::Detail::stringify( '\0' ) == "0" for: "0" == "0" ok {test-number} - ::Catch::Detail::stringify( static_cast(2) ) == "2" for: "2" == "2" # Character pretty printing ok {test-number} - ::Catch::Detail::stringify( static_cast(5) ) == "5" for: "5" == "5" +# Clara::Arg does not crash on incomplete input +ok {test-number} - name.empty() for: true +# Clara::Arg does not crash on incomplete input +ok {test-number} - result for: {?} +# Clara::Arg does not crash on incomplete input +ok {test-number} - result.type() == Catch::Clara::Detail::ResultType::Ok for: 0 == 0 +# Clara::Arg does not crash on incomplete input +ok {test-number} - parsed.type() == Catch::Clara::ParseResultType::NoMatch for: 1 == 1 +# Clara::Arg does not crash on incomplete input +ok {test-number} - parsed.remainingTokens().count() == 2 for: 2 == 2 +# Clara::Arg does not crash on incomplete input +ok {test-number} - name.empty() for: true # Clara::Arg supports single-arg parse the way Opt does ok {test-number} - name.empty() for: true # Clara::Arg supports single-arg parse the way Opt does @@ -4536,5 +4548,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2271 +1..2277 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index d305ee83..9a59fa6d 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -245,6 +245,8 @@ ##teamcity[testFinished name='CaseInsensitiveLess is case insensitive' duration="{duration}"] ##teamcity[testStarted name='Character pretty printing'] ##teamcity[testFinished name='Character pretty printing' duration="{duration}"] +##teamcity[testStarted name='Clara::Arg does not crash on incomplete input'] +##teamcity[testFinished name='Clara::Arg does not crash on incomplete input' duration="{duration}"] ##teamcity[testStarted name='Clara::Arg supports single-arg parse the way Opt does'] ##teamcity[testFinished name='Clara::Arg supports single-arg parse the way Opt does' duration="{duration}"] ##teamcity[testStarted name='Clara::Opt supports accept-many lambdas'] diff --git a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt index 156a8e2c..989a37fe 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.multi.approved.txt @@ -245,6 +245,8 @@ ##teamcity[testFinished name='CaseInsensitiveLess is case insensitive' duration="{duration}"] ##teamcity[testStarted name='Character pretty printing'] ##teamcity[testFinished name='Character pretty printing' duration="{duration}"] +##teamcity[testStarted name='Clara::Arg does not crash on incomplete input'] +##teamcity[testFinished name='Clara::Arg does not crash on incomplete input' duration="{duration}"] ##teamcity[testStarted name='Clara::Arg supports single-arg parse the way Opt does'] ##teamcity[testFinished name='Clara::Arg supports single-arg parse the way Opt does' duration="{duration}"] ##teamcity[testStarted name='Clara::Opt supports accept-many lambdas'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index a00e4577..c64c5a7b 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -3304,6 +3304,57 @@ Approx( 1.23399996757507324 ) + + + + name.empty() + + + true + + + + + result + + + {?} + + + + + result.type() == Catch::Clara::Detail::ResultType::Ok + + + 0 == 0 + + + + + parsed.type() == Catch::Clara::ParseResultType::NoMatch + + + 1 == 1 + + + + + parsed.remainingTokens().count() == 2 + + + 2 == 2 + + + + + name.empty() + + + true + + + + @@ -21882,6 +21933,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 08a1804a..d35ba1af 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -3304,6 +3304,57 @@ Approx( 1.23399996757507324 ) + + + + name.empty() + + + true + + + + + result + + + {?} + + + + + result.type() == Catch::Clara::Detail::ResultType::Ok + + + 0 == 0 + + + + + parsed.type() == Catch::Clara::ParseResultType::NoMatch + + + 1 == 1 + + + + + parsed.remainingTokens().count() == 2 + + + 2 == 2 + + + + + name.empty() + + + true + + + + @@ -21881,6 +21932,6 @@ Approx( -1.95996398454005449 ) - - + + diff --git a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp index 14ba433d..53023b50 100644 --- a/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp +++ b/tests/SelfTest/IntrospectiveTests/Clara.tests.cpp @@ -51,6 +51,21 @@ TEST_CASE("Clara::Arg supports single-arg parse the way Opt does", "[clara][arg] REQUIRE(name == "foo"); } +TEST_CASE("Clara::Arg does not crash on incomplete input", "[clara][arg][compilation]") { + std::string name; + auto p = Catch::Clara::Arg(name, "-"); + + CHECK(name.empty()); + + auto result = p.parse( Catch::Clara::Args{ "UnitTest", "-" } ); + CHECK( result ); + CHECK( result.type() == Catch::Clara::Detail::ResultType::Ok ); + const auto& parsed = result.value(); + CHECK( parsed.type() == Catch::Clara::ParseResultType::NoMatch ); + CHECK( parsed.remainingTokens().count() == 2 ); + CHECK( name.empty() ); +} + TEST_CASE("Clara::Opt supports accept-many lambdas", "[clara][opt]") { using namespace Catch::Clara; std::vector res;