Skip to content

Commit 89c3f61

Browse files
committed
Add checked child iteration to items
1 parent 56479b9 commit 89c3f61

4 files changed

Lines changed: 351 additions & 29 deletions

File tree

doc/ref/cbor/cbor_view.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class item
164164
tag_range tags() const noexcept; // leading tags, outermost first
165165

166166
chunk_range chunks() const noexcept; // string content spans
167+
child_range children(scan_context& context) const noexcept; // raw children as items
167168

168169
bool uint64_value(uint64_t& value) const noexcept;
169170
bool int64_value(int64_t& value) const noexcept;
@@ -191,9 +192,15 @@ length, a container's count, a simple value's number, or the bit
191192
pattern of a floating-point value.
192193

193194
`chunks()` yields the contiguous spans of a string's content, one per chunk
194-
for indefinite-length strings. Structural container traversal is intentionally
195-
provided only by `navigator`, avoiding a second iterator state machine and its
196-
hidden subtree rescans.
195+
for indefinite-length strings.
196+
197+
`children(context)` yields a container's raw children in order — array
198+
elements, or a map's keys and values alternating — each a complete checked
199+
item, measured once on the way past. The item's bytes are checked, so
200+
iteration cannot fail; the context, which must outlive the range, supplies
201+
skip workspace for container children and grows only past 32 open
202+
containers. `children` serves sibling iteration over checked bytes;
203+
`navigator` serves stateful traversal with retained parents and extents.
197204

198205
The typed accessors return `false`, leaving `value` untouched, exactly
199206
when the item is not of the requested kind; conversions are strict.

fuzzers/fuzz_cbor_view.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,53 @@ namespace {
280280
require(reset.value().size() == navigated.value().remainder.size());
281281
}
282282

283+
// Children agree with navigator movement over the same container.
284+
void exercise_children(byte_span input, scan_context& context)
285+
{
286+
auto scanned = scan_prefix(input, context);
287+
if (!scanned.has_value())
288+
{
289+
return;
290+
}
291+
const item root = scanned.value().first;
292+
293+
auto navigated = navigate_prefix(input);
294+
require(navigated.has_value());
295+
navigator nav = std::move(navigated.value().first);
296+
297+
auto it = root.children(context).begin();
298+
if (!nav.enter())
299+
{
300+
require(root.children(context).empty());
301+
require(it == item::child_iterator());
302+
return;
303+
}
304+
require(!root.children(context).empty());
305+
306+
std::size_t count = 0;
307+
for (;;)
308+
{
309+
require(it != item::child_iterator());
310+
const item child = *it;
311+
require(child.kind() == nav.kind());
312+
require(child.argument() == nav.argument());
313+
require(child.indefinite() == nav.indefinite());
314+
const item finished = nav.finish_item();
315+
require(finished.encoded_bytes().data() == child.encoded_bytes().data());
316+
require(finished.encoded_bytes().size() == child.encoded_bytes().size());
317+
++it;
318+
if (++count > 4096)
319+
{
320+
return;
321+
}
322+
if (!nav.next())
323+
{
324+
break;
325+
}
326+
}
327+
require(it == item::child_iterator());
328+
}
329+
283330

284331
void exercise_input(byte_span input, scan_context& context)
285332
{
@@ -347,6 +394,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, std::size_t size)
347394
scan_context context(depth);
348395
exercise_input(input, context);
349396
exercise_navigator(input, depth);
397+
exercise_children(input, context);
350398
exercise_input(byte_span(base, mid), context);
351399
exercise_navigator(byte_span(base, mid), depth);
352400
exercise_input(byte_span(base + mid, size - mid), context);

include/jsoncons_ext/cbor/cbor_view.hpp

Lines changed: 188 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ namespace view {
582582

583583
} // namespace detail_view
584584

585+
class scan_context;
586+
585587
// A checked, zero-copy view of one complete, structurally well-formed CBOR
586588
// item: its leading semantic tags, head, and content. Obtained from
587589
// scan_prefix, parse_exact, wire_cursor, or navigator::finish_item; never
@@ -619,6 +621,8 @@ namespace view {
619621
class tag_range;
620622
class chunk_iterator;
621623
class chunk_range;
624+
class child_iterator;
625+
class child_range;
622626

623627
// The item's leading semantic tags, outermost first. Empty for
624628
// untagged items. Tags are exposed, never interpreted: deciding what
@@ -630,6 +634,13 @@ namespace view {
630634
// one. Empty unless kind() is byte_string or text_string.
631635
chunk_range chunks() const noexcept;
632636

637+
// The raw children of a container item, each a checked item: array
638+
// elements, or a map's keys and values alternating. Empty unless
639+
// kind() is array or map. The item's bytes are checked, so iteration
640+
// cannot fail; the context, which must outlive the range, supplies
641+
// skip workspace and grows only past 32 open containers.
642+
child_range children(scan_context& context) const noexcept;
643+
633644
// The typed accessors return false, leaving `value` untouched, on a
634645
// kind mismatch. Structural well-formedness was established by scanning.
635646
bool uint64_value(uint64_t& value) const noexcept
@@ -711,8 +722,6 @@ namespace view {
711722
}
712723
};
713724

714-
class scan_context;
715-
716725
// Offset-based access to unchecked CBOR wire data. The cursor borrows its
717726
// input and never exposes a mutable pointer/end pair.
718727
class wire_cursor
@@ -1310,6 +1319,166 @@ namespace view {
13101319

13111320
} // namespace detail_view
13121321

1322+
// Iterates the raw children of a checked container item, measuring each
1323+
// child once to yield it as a complete checked item.
1324+
class item::child_iterator
1325+
{
1326+
public:
1327+
using value_type = item;
1328+
using reference = item;
1329+
using pointer = void;
1330+
using difference_type = std::ptrdiff_t;
1331+
using iterator_category = std::input_iterator_tag;
1332+
1333+
child_iterator() noexcept
1334+
: pos_(nullptr), next_(nullptr), end_(nullptr), remaining_(0),
1335+
content_(nullptr), context_(nullptr)
1336+
{
1337+
}
1338+
1339+
item operator*() const noexcept
1340+
{
1341+
assert(pos_ != nullptr);
1342+
return detail_view::item_access::make(
1343+
span<const uint8_t>(pos_, static_cast<std::size_t>(next_ - pos_)),
1344+
head_, content_);
1345+
}
1346+
1347+
child_iterator& operator++()
1348+
{
1349+
advance();
1350+
return *this;
1351+
}
1352+
1353+
child_iterator operator++(int)
1354+
{
1355+
child_iterator temp = *this;
1356+
advance();
1357+
return temp;
1358+
}
1359+
1360+
friend bool operator==(const child_iterator& a, const child_iterator& b) noexcept
1361+
{
1362+
return a.pos_ == b.pos_;
1363+
}
1364+
1365+
friend bool operator!=(const child_iterator& a, const child_iterator& b) noexcept
1366+
{
1367+
return a.pos_ != b.pos_;
1368+
}
1369+
1370+
private:
1371+
friend class child_range;
1372+
1373+
child_iterator(const uint8_t* first, const uint8_t* end, uint64_t remaining,
1374+
scan_context& context) noexcept
1375+
: pos_(nullptr), next_(first), end_(end), remaining_(remaining),
1376+
content_(nullptr), context_(&context)
1377+
{
1378+
advance();
1379+
}
1380+
1381+
void advance()
1382+
{
1383+
pos_ = next_;
1384+
if (remaining_ == detail_view::indefinite_array_marker)
1385+
{
1386+
if (*pos_ == 0xff)
1387+
{
1388+
pos_ = nullptr;
1389+
return;
1390+
}
1391+
}
1392+
else if (remaining_ == 0)
1393+
{
1394+
pos_ = nullptr;
1395+
return;
1396+
}
1397+
else
1398+
{
1399+
--remaining_;
1400+
}
1401+
1402+
const uint8_t* p = pos_;
1403+
std::error_code ec;
1404+
bool ok = detail_view::read_value_head(p, end_, head_, ec);
1405+
assert(ok && !ec);
1406+
content_ = p;
1407+
ok = head_.major_type == cbor::detail::cbor_major_type::array ||
1408+
head_.major_type == cbor::detail::cbor_major_type::map
1409+
? detail_view::skip_container(p, end_, head_,
1410+
(std::numeric_limits<int>::max)(),
1411+
detail_view::scan_access::workspace(*context_), ec)
1412+
: detail_view::skip_scalar_or_string(head_, p, end_, ec);
1413+
assert(ok && !ec);
1414+
(void)ok;
1415+
next_ = p;
1416+
}
1417+
1418+
const uint8_t* pos_; // current child's begin, nullptr when exhausted
1419+
const uint8_t* next_; // current child's end, the next child's begin
1420+
const uint8_t* end_; // the parent's end
1421+
uint64_t remaining_; // raw children left; indefinite ends at a break
1422+
detail_view::item_head head_;
1423+
const uint8_t* content_;
1424+
scan_context* context_;
1425+
};
1426+
1427+
class item::child_range
1428+
{
1429+
public:
1430+
using iterator = child_iterator;
1431+
using const_iterator = child_iterator;
1432+
1433+
child_iterator begin() const
1434+
{
1435+
if (first_ == nullptr)
1436+
{
1437+
return child_iterator();
1438+
}
1439+
return child_iterator(first_, end_, remaining_, *context_);
1440+
}
1441+
1442+
child_iterator end() const noexcept
1443+
{
1444+
return child_iterator();
1445+
}
1446+
1447+
bool empty() const noexcept
1448+
{
1449+
return first_ == nullptr ||
1450+
(remaining_ == detail_view::indefinite_array_marker
1451+
? *first_ == 0xff : remaining_ == 0);
1452+
}
1453+
1454+
private:
1455+
friend class item;
1456+
child_range(const uint8_t* first, const uint8_t* end, uint64_t remaining,
1457+
scan_context* context) noexcept
1458+
: first_(first), end_(end), remaining_(remaining), context_(context)
1459+
{
1460+
}
1461+
1462+
const uint8_t* first_;
1463+
const uint8_t* end_;
1464+
uint64_t remaining_;
1465+
scan_context* context_;
1466+
};
1467+
1468+
inline item::child_range item::children(scan_context& context) const noexcept
1469+
{
1470+
if (head_.major_type != cbor::detail::cbor_major_type::array &&
1471+
head_.major_type != cbor::detail::cbor_major_type::map)
1472+
{
1473+
return child_range(nullptr, nullptr, 0, &context);
1474+
}
1475+
const uint64_t remaining = head_.indefinite()
1476+
? detail_view::indefinite_array_marker
1477+
: (head_.major_type == cbor::detail::cbor_major_type::map
1478+
? 2 * head_.value : head_.value);
1479+
return child_range(content_, bytes_.data() + bytes_.size(), remaining, &context);
1480+
}
1481+
13131482
struct scan_result
13141483
{
13151484
item first;
@@ -1858,41 +2027,34 @@ namespace view {
18582027
expected<bool, scan_error> map_keys_sorted(span<const uint8_t> input,
18592028
Order order = Order(), int max_nesting_depth = default_max_nesting_depth)
18602029
{
1861-
auto result = navigate_prefix(input, max_nesting_depth);
1862-
if (!result)
2030+
scan_context context(max_nesting_depth);
2031+
auto scanned = scan_prefix(input, context);
2032+
if (!scanned)
18632033
{
1864-
return expected<bool, scan_error>(unexpect, result.error());
2034+
return expected<bool, scan_error>(unexpect, scanned.error());
18652035
}
1866-
1867-
navigator nav = std::move(result.value().first);
1868-
if (nav.kind() != item_kind::map)
2036+
const item map_item = scanned.value().first;
2037+
if (map_item.kind() != item_kind::map)
18692038
{
18702039
return false;
18712040
}
18722041

18732042
span<const uint8_t> previous;
1874-
if (!nav.enter())
1875-
{
1876-
return true;
1877-
}
1878-
for (;;)
2043+
bool is_key = true;
2044+
for (item child : map_item.children(context))
18792045
{
1880-
assert(nav.role() == position_role::map_key);
1881-
const span<const uint8_t> key = nav.finish_item().encoded_bytes();
1882-
if (!previous.empty() && order(previous, key) >= 0)
2046+
if (is_key)
18832047
{
1884-
return false;
1885-
}
1886-
previous = key;
1887-
1888-
const bool has_value = nav.next();
1889-
assert(has_value && nav.role() == position_role::map_value);
1890-
(void)has_value;
1891-
if (!nav.next())
1892-
{
1893-
return true;
2048+
const span<const uint8_t> key = child.encoded_bytes();
2049+
if (!previous.empty() && order(previous, key) >= 0)
2050+
{
2051+
return false;
2052+
}
2053+
previous = key;
18942054
}
2055+
is_key = !is_key;
18952056
}
2057+
return true;
18962058
}
18972059

18982060
// True if `text_item` is a text string whose content is well-formed

0 commit comments

Comments
 (0)