Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions include/glaze/util/parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1246,13 +1246,27 @@ namespace glz
return {};
}

// null_terminated == false means the buffer has no trailing sentinel, so each peek that
// follows an advance past a digit run is guarded against end. A number whose last digit sits
// at end is complete, so those guards return without error; only a dangling sign/exponent is
// a syntax error.
template <bool null_terminated = true>
GLZ_ALWAYS_INLINE void skip_number_with_validation(is_context auto&& ctx, auto&& it, auto end) noexcept
{
it += *it == '-';
const auto sig_start_it = it;
auto frac_start_it = end;
if constexpr (not null_terminated) {
if (it == end) {
ctx.error = error_code::unexpected_end;
return;
}
}
if (*it == '0') {
++it;
if constexpr (not null_terminated) {
if (it == end) return;
}
if (*it != '.') {
return;
}
Expand All @@ -1264,6 +1278,9 @@ namespace glz
ctx.error = error_code::syntax_error;
return;
}
if constexpr (not null_terminated) {
if (it == end) return;
}
if ((*it | ('E' ^ 'e')) == 'e') {
++it;
goto exp_start;
Expand All @@ -1277,9 +1294,18 @@ namespace glz
ctx.error = error_code::syntax_error;
return;
}
if constexpr (not null_terminated) {
if (it == end) return;
}
if ((*it | ('E' ^ 'e')) != 'e') return;
++it;
exp_start:
if constexpr (not null_terminated) {
if (it == end) {
ctx.error = error_code::syntax_error;
return;
}
}
it += *it == '+' || *it == '-';
const auto exp_start_it = it;
it = std::find_if_not(it, end, is_digit);
Expand All @@ -1293,26 +1319,37 @@ namespace glz
struct skip_number_opts
{
bool validate;
bool null_terminated;

// Convert from any opts-like type (consteval because check_validate_skipped is consteval)
// Convert from any opts-like type (consteval because check_* functions are consteval)
template <typename T>
consteval skip_number_opts(const T& opts) noexcept : validate{check_validate_skipped(opts)}
consteval skip_number_opts(const T& opts) noexcept
: validate{check_validate_skipped(opts)}, null_terminated{check_null_terminated(opts)}
{}

// Direct construction
explicit consteval skip_number_opts(bool validate_) noexcept : validate{validate_} {}
consteval skip_number_opts(bool validate_, bool null_terminated_) noexcept
: validate{validate_}, null_terminated{null_terminated_}
{}
};

template <skip_number_opts Opts>
GLZ_ALWAYS_INLINE void skip_number(is_context auto&& ctx, auto&& it, auto end) noexcept
{
if constexpr (not Opts.validate) {
while (numeric_table[uint8_t(*it)]) {
++it;
if constexpr (Opts.null_terminated) {
while (numeric_table[uint8_t(*it)]) {
++it;
}
}
else {
while (it < end && numeric_table[uint8_t(*it)]) {
++it;
}
}
}
else {
skip_number_with_validation(ctx, it, end);
skip_number_with_validation<Opts.null_terminated>(ctx, it, end);
}
}

Expand Down
32 changes: 32 additions & 0 deletions tests/json_test/json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2312,6 +2312,38 @@ suite early_end = [] {
}
};

"early_end !null terminated number skip"_test = [] {
// get_view_json reaches the validating value skip (parse_value), which routes numbers
// through skip_number_with_validation. On a non-null-terminated buffer that ends right
// after a number's digits there is no sentinel, so the continuation peeks must stop at
// end rather than read past it. Truncate after each byte and confirm every pointer read
// stays in bounds (ASAN container annotations catch an over-read here).
static constexpr glz::opts options{.null_terminated = false};

std::string_view buffer_data = R"({"a":0,"b":123,"c":-5,"d":3.14,"e":1e10,"f":2E-3,"g":0.5e+2,"h":-0})";
std::vector<char> temp{buffer_data.begin(), buffer_data.end()};
std::string_view buffer{temp.data(), temp.data() + temp.size()};

while (buffer.size() > 0) {
temp.pop_back();
buffer = {temp.data(), temp.data() + temp.size()};
// Each pointer targets a numeric leaf; truncation eventually ends the buffer mid- or
// post-number. None of these may read past end regardless of whether they succeed.
(void)glz::get_view_json<"/a", options>(buffer);
(void)glz::get_view_json<"/d", options>(buffer);
(void)glz::get_view_json<"/e", options>(buffer);
(void)glz::get_view_json<"/g", options>(buffer);
(void)glz::get_view_json<"/h", options>(buffer);
}

// The complete buffer still resolves numeric leaves to their exact spans.
std::string_view full = buffer_data;
const auto as_sv = [](auto&& span) { return std::string_view{span.data(), span.size()}; };
expect(as_sv(glz::get_view_json<"/b", options>(full).value()) == "123");
expect(as_sv(glz::get_view_json<"/e", options>(full).value()) == "1e10");
expect(as_sv(glz::get_view_json<"/g", options>(full).value()) == "0.5e+2");
};

trace.end("early_end");
};

Expand Down
Loading