Skip to content
Open
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
38 changes: 23 additions & 15 deletions include/fkYAML/detail/conversions/from_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,31 +362,39 @@ struct from_node_int_helper {
}
};

/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type.
/// @brief Partial specialization for uint64_t when integer_type != uint64_t (the common int64_t case).
/// This must be declared BEFORE the generic <IntType, false> specialization so the compiler always
/// prefers it for uint64_t. Using a hardcoded 'false' (not a value-dependent expression) avoids
/// the MSVC ambiguity that arises when std::is_same<...>::value is used as a template argument.
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type)
template <typename BasicNodeType>
struct from_node_int_helper<BasicNodeType, uint64_t, false> {
/// @brief Convert node's integer value to uint64_t via as_uint().
/// @param n A node object.
/// @return The node value as uint64_t.
static uint64_t convert(const BasicNodeType& n) {
return n.as_uint();
}
};

/// @brief Helper struct for node-to-int conversion if IntType is not the node's integer value type
/// and IntType is not uint64_t (covered by the explicit specialization above).
/// @tparam BasicNodeType A basic_node template instance type.
/// @tparam IntType Target integer value type (different from BasicNodeType::integer_type, not uint64_t)
template <typename BasicNodeType, typename IntType>
struct from_node_int_helper<BasicNodeType, IntType, false> {
/// @brief Convert node's integer value to non-uint64_t integer types.
/// @brief Convert node's integer value to a narrower signed/unsigned integer type.
/// @param n A node object.
/// @return An integer value converted from the node's integer value.
static IntType convert(const BasicNodeType& n) {
using node_int_type = typename BasicNodeType::integer_type;
const node_int_type tmp_int = n.as_int();

// under/overflow check.
if (std::is_same<IntType, uint64_t>::value) {
if FK_YAML_UNLIKELY (tmp_int < 0) {
throw exception("Integer value underflow detected.");
}
if FK_YAML_UNLIKELY (tmp_int < static_cast<node_int_type>(std::numeric_limits<IntType>::min())) {
throw exception("Integer value underflow detected.");
}
else {
if FK_YAML_UNLIKELY (tmp_int < static_cast<node_int_type>(std::numeric_limits<IntType>::min())) {
throw exception("Integer value underflow detected.");
}
if FK_YAML_UNLIKELY (static_cast<node_int_type>(std::numeric_limits<IntType>::max()) < tmp_int) {
throw exception("Integer value overflow detected.");
}
if FK_YAML_UNLIKELY (static_cast<node_int_type>(std::numeric_limits<IntType>::max()) < tmp_int) {
throw exception("Integer value overflow detected.");
}

return static_cast<IntType>(tmp_int);
Expand Down
14 changes: 13 additions & 1 deletion include/fkYAML/detail/conversions/to_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ struct external_node_constructor {
n.m_value.integer = i;
}

/// @brief Constructs an INTEGER node from a uint64_t value that exceeds the signed range.
/// The raw bit pattern is stored in the integer field and the uint_bit flag is set so that
/// get_value<uint64_t>() / as_uint() can recover the original unsigned value.
static void unsigned_integer_scalar(BasicNodeType& n, const typename BasicNodeType::integer_type i) {
destroy(n);
n.m_attrs |= node_attr_bits::int_bit;
n.m_attrs |= node_attr_bits::uint_bit;
n.m_value.integer = i;
}

static void float_scalar(BasicNodeType& n, const typename BasicNodeType::float_number_type f) {
destroy(n);
n.m_attrs |= node_attr_bits::float_bit;
Expand All @@ -81,7 +91,9 @@ struct external_node_constructor {
private:
static void destroy(BasicNodeType& n) {
n.m_value.destroy(n.m_attrs & node_attr_mask::value);
n.m_attrs &= ~node_attr_mask::value;
// Clear both the value-type bits and the uint_bit style flag so that any
// subsequent reassignment starts from a clean state.
n.m_attrs &= ~(node_attr_mask::value | node_attr_bits::uint_bit);
}
};

Expand Down
19 changes: 19 additions & 0 deletions include/fkYAML/detail/input/scalar_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <fkYAML/detail/macros/define_macros.hpp>
#include <fkYAML/detail/assert.hpp>
#include <fkYAML/detail/conversions/scalar_conv.hpp>
#include <fkYAML/detail/conversions/to_node.hpp>
#include <fkYAML/detail/encodings/yaml_escaper.hpp>
#include <fkYAML/detail/input/block_scalar_header.hpp>
#include <fkYAML/detail/input/scalar_scanner.hpp>
Expand Down Expand Up @@ -534,6 +535,24 @@ class scalar_parser {
if FK_YAML_LIKELY (converted) {
return basic_node_type(integer);
}

// For untagged plain integer scalars, attempt a uint64_t parse to handle large
// positive values that exceed int64_t max (e.g. xxHash/UUID results like
// 15745692345339290292). This only applies when integer_type is a signed 64-bit
// type; any other width would not be able to represent the value anyway.
if (tag_type != tag_t::INTEGER && std::is_signed<integer_type>::value &&
sizeof(integer_type) == sizeof(uint64_t)) {
uint64_t u64 = 0;
if (detail::atoi(token.begin(), token.end(), u64)) {
basic_node_type node;
// Store the bit pattern in the signed field and set uint_bit so that
// as_uint() / get_value<uint64_t>() can recover the correct value.
detail::external_node_constructor<basic_node_type>::unsigned_integer_scalar(
node, static_cast<integer_type>(u64));
return node;
}
}

if FK_YAML_UNLIKELY (tag_type == tag_t::INTEGER) {
throw parse_error("Failed to convert a scalar to an integer.", m_line, m_indent);
}
Expand Down
5 changes: 5 additions & 0 deletions include/fkYAML/detail/node_attrs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ constexpr node_attr_t string_bit = 1u << 6;
/// A utility bit set to filter scalar node bits.
constexpr node_attr_t scalar_bits = null_bit | bool_bit | int_bit | float_bit | string_bit;

/// The unsigned integer flag bit.
/// Set on INTEGER nodes whose stored int64_t value represents a uint64_t that exceeds INT64_MAX.
/// This allows values such as xxHash/UUID results to round-trip correctly through get_value<uint64_t>().
constexpr node_attr_t uint_bit = 1u << 16; // lives in the style bits area (0x00FF0000)

/// The anchor node bit.
constexpr node_attr_t anchor_bit = 0x01000000u;
/// The alias node bit.
Expand Down
50 changes: 49 additions & 1 deletion include/fkYAML/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1472,25 +1472,67 @@ class basic_node {
throw fkyaml::type_error("The node value is not a boolean.", get_type());
}

/// @brief Checks if the node is an integer that was parsed from a uint64_t value exceeding INT64_MAX.
/// @return true if the node holds an unsigned integer, false otherwise.
bool is_uint() const noexcept {
return resolve_reference().is_uint_impl();
}

/// @brief Returns the integer node value as an unsigned 64-bit integer.
/// This is valid both for nodes where integer_type is unsigned and for nodes where a large
/// positive decimal scalar (> INT64_MAX) was stored with the uint_bit flag set.
/// @throw fkyaml::type_error if the node is not a compatible integer.
/// @return The node value as uint64_t.
uint64_t as_uint() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_integer_impl()) {
// When integer_type is unsigned the stored value IS the uint64_t directly.
if (std::is_unsigned<integer_type>::value) {
return static_cast<uint64_t>(act_node.m_value.integer);
}
// When integer_type is signed, only uint_bit-marked nodes carry a uint64_t.
if (act_node.m_attrs & detail::node_attr_bits::uint_bit) {
return static_cast<uint64_t>(act_node.m_value.integer);
}
// Signed values in the non-negative range can be returned safely.
if (act_node.m_value.integer >= static_cast<integer_type>(0)) {
return static_cast<uint64_t>(act_node.m_value.integer);
}
}
throw fkyaml::type_error("The node value cannot be represented as an unsigned integer.", get_type());
}

/// @brief Returns reference to the integer node value.
/// @throw fkyaml::type_error The node value is not an integer.
/// @return Reference to the integer node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_int/
integer_type& as_int() {
basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_integer_impl()) {
if FK_YAML_UNLIKELY (act_node.is_uint_impl()) {
throw fkyaml::type_error(
"The integer value exceeds INT64_MAX and cannot be returned as a signed integer. "
"Use as_uint() instead.",
get_type());
}
return act_node.m_value.integer;
}
throw fkyaml::type_error("The node value is not an integer.", get_type());
}

/// @brief Returns reference to the integer node value.
/// @throw fkyaml::type_error The node value is not an integer.
/// @throw fkyaml::type_error The node value is not an integer, or exceeds INT64_MAX.
/// @return Constant reference to the integer node value.
/// @sa https://fktn-k.github.io/fkYAML/api/basic_node/as_int/
const integer_type& as_int() const {
const basic_node& act_node = resolve_reference();
if FK_YAML_LIKELY (act_node.is_integer_impl()) {
if FK_YAML_UNLIKELY (act_node.is_uint_impl()) {
throw fkyaml::type_error(
"The integer value exceeds INT64_MAX and cannot be returned as a signed integer. "
"Use as_uint() instead.",
get_type());
}
return act_node.m_value.integer;
}
throw fkyaml::type_error("The node value is not an integer.", get_type());
Expand Down Expand Up @@ -1768,6 +1810,12 @@ class basic_node {
return m_attrs & detail::node_attr_bits::int_bit;
}

bool is_uint_impl() const noexcept {
// Both int_bit and uint_bit must be set: this node stores a uint64_t value
// whose bit pattern was placed into the signed integer_type field.
return (m_attrs & detail::node_attr_bits::int_bit) && (m_attrs & detail::node_attr_bits::uint_bit);
}

bool is_float_number_impl() const noexcept {
return m_attrs & detail::node_attr_bits::float_bit;
}
Expand Down
Loading
Loading