Skip to content

Commit c76cfef

Browse files
authored
Merge pull request #63 from sawenzel/cfg/dynamic-improvements
ConfigurableParam: reject malformed container fields, drop redundant parseSet
2 parents 1baae2d + 2ed6b7b commit c76cfef

2 files changed

Lines changed: 53 additions & 33 deletions

File tree

Common/Utils/include/CommonUtils/ConfigurableParam.h

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,6 @@ concept MapLike = Container<T> && requires {
162162
template <typename T>
163163
concept SequenceContainer = Container<T> && !MapLike<T>;
164164

165-
template <typename T>
166-
concept HasPushBack = requires(T a, typename T::value_type v) {
167-
{ a.push_back(v) } -> std::same_as<void>;
168-
};
169-
170165
template <typename>
171166
inline constexpr bool AlwaysFalse = false;
172167

@@ -178,9 +173,12 @@ class ContainerParser
178173
{
179174
if constexpr (MapLike<T>) {
180175
return parseMap<T>(str);
181-
} else if constexpr (SequenceContainer<T>) {
182-
return parseSet<T>(str);
183176
} else if constexpr (Container<T>) {
177+
// Covers vector/list/deque as well as set/unordered_set: parseSequence
178+
// inserts at end(), which both sequence and associative-set containers
179+
// accept. (Any non-map Container is a SequenceContainer, so the previous
180+
// separate parseSet branch was unreachable and re-parsed into a temporary
181+
// vector first.)
184182
return parseSequence<T>(str);
185183
} else {
186184
return parseScalar<T>(str);
@@ -198,7 +196,7 @@ class ContainerParser
198196
}
199197

200198
private:
201-
// Parse vector, list, deque, array
199+
// Parse sequence and set containers (vector, list, deque, set, unordered_set)
202200
template <SequenceContainer SequenceT>
203201
static SequenceT parseSequence(const std::string& str)
204202
{
@@ -251,23 +249,6 @@ class ContainerParser
251249
return result;
252250
}
253251

254-
// Parse set containers
255-
template <SequenceContainer SetT>
256-
static SetT parseSet(const std::string& str)
257-
{
258-
SetT result;
259-
using ValueType = typename SetT::value_type;
260-
auto vec = parseSequence<std::vector<ValueType>>(str);
261-
for (const auto& val : vec) {
262-
if constexpr (HasPushBack<SetT>) {
263-
result.push_back(val);
264-
} else {
265-
result.insert(val);
266-
}
267-
}
268-
return result;
269-
}
270-
271252
// Parse scalar types
272253
template <typename T>
273254
static T parseScalar(const std::string& str)
@@ -357,17 +338,17 @@ class ContainerParser
357338
} else if (c == '}') {
358339
brace_depth--;
359340
} else if (c == delimiter && bracket_depth == 0 && brace_depth == 0) {
360-
if (!current.empty()) {
361-
tokens.push_back(current);
362-
current.clear();
363-
}
341+
// Keep empty fields: a stray delimiter (e.g. "[1,,3]" or "key:") must
342+
// surface as a parse error downstream rather than silently dropping an
343+
// element. The empty-container case ("[]"/"{}") is handled by the
344+
// callers before split() is ever reached.
345+
tokens.push_back(current);
346+
current.clear();
364347
continue;
365348
}
366349
current += c;
367350
}
368-
if (!current.empty()) {
369-
tokens.push_back(current);
370-
}
351+
tokens.push_back(current);
371352
return tokens;
372353
}
373354
};

Common/Utils/test/testConfigurableParam.cxx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
#include <boost/test/unit_test.hpp>
1919
#include <boost/property_tree/ptree.hpp>
2020
#include <cstdlib>
21+
#include <deque>
2122
#include <filesystem>
23+
#include <list>
24+
#include <set>
25+
#include <unordered_set>
26+
#include <vector>
2227

2328
#include "CommonUtils/ConfigurableParamTest.h"
2429

@@ -200,6 +205,40 @@ BOOST_AUTO_TEST_CASE(ConfigurableParam_ContainerParserMap)
200205
BOOST_CHECK_EQUAL(m["beta"], 0.3);
201206
}
202207

208+
BOOST_AUTO_TEST_CASE(ConfigurableParam_ContainerParserSetAndSequenceTypes)
209+
{
210+
// All non-map containers go through the single parseSequence path. Verify the
211+
// set containers (which previously took a separate, doubly-parsing parseSet
212+
// branch) still parse and de-duplicate correctly.
213+
auto se = ContainerParser::parse<std::set<int>>("[2,3,2,3,2,5]");
214+
BOOST_CHECK_EQUAL(se.size(), 3);
215+
BOOST_CHECK(se == (std::set<int>{2, 3, 5}));
216+
217+
auto us = ContainerParser::parse<std::unordered_set<int>>("[1,2,3,3]");
218+
BOOST_CHECK_EQUAL(us.size(), 3);
219+
BOOST_CHECK(us.count(1) && us.count(2) && us.count(3));
220+
221+
auto li = ContainerParser::parse<std::list<int>>("[7,8]");
222+
BOOST_CHECK(li == (std::list<int>{7, 8}));
223+
224+
auto dq = ContainerParser::parse<std::deque<int>>("[9,10]");
225+
BOOST_CHECK(dq == (std::deque<int>{9, 10}));
226+
}
227+
228+
BOOST_AUTO_TEST_CASE(ConfigurableParam_ContainerParserRejectsEmptyToken)
229+
{
230+
// A stray delimiter must be reported, not silently swallowed: "[1,,3]" used to
231+
// parse to a 2-element vector with no error, masking malformed configuration.
232+
BOOST_CHECK_THROW(ContainerParser::parse<std::vector<int>>("[1,,3]"), std::exception);
233+
BOOST_CHECK_THROW(ContainerParser::parse<std::vector<int>>("[1,2,]"), std::exception);
234+
// ... and on the map side, an empty value/entry is rejected too.
235+
BOOST_CHECK_THROW((ContainerParser::parse<std::map<int, int>>("{1:2,,3:4}")), std::exception);
236+
BOOST_CHECK_THROW((ContainerParser::parse<std::map<int, int>>("{1:}")), std::exception);
237+
// Well-formed input is unaffected.
238+
auto v = ContainerParser::parse<std::vector<int>>("[1,2,3]");
239+
BOOST_CHECK_EQUAL(v.size(), 3);
240+
}
241+
203242
BOOST_AUTO_TEST_CASE(ConfigurableParam_DamerauLevenshteinDistance)
204243
{
205244
BOOST_CHECK_EQUAL(damerauLevenshteinDistance("TestParam.iValue", "TestParam.iValue"), 0);
@@ -255,5 +294,5 @@ BOOST_AUTO_TEST_CASE(ConfigurableParam_Container_FileIO_ROOT)
255294
for (const auto& s : set) {
256295
BOOST_CHECK(tp.set.contains(s));
257296
}
258-
// std::remove(testFileName.c_str());
297+
std::remove(testFileName.c_str());
259298
}

0 commit comments

Comments
 (0)