Add support for parsing NaN and Infinity as requested in #1540, #2414, and #2540 (#2696)

* add compile option 'SIMDJSON_ENABLE_NAN_INF' but disable by default

* extend parser to support NaN/Infinity when SIMDJSON_ENABLE_NAN_INF=1

* update tests to check parsing of NaN/Infinity, when enabled

* update minefield tests: mark nan/inf tests as passing when nan/inf is ON

* update CI/CD to run tests with extensions for NaN/Infinity enabled
This commit is contained in:
Alecto Irene Perez
2026-05-04 15:24:26 -04:00
committed by GitHub
parent f902769b35
commit b9b20be80e
17 changed files with 978 additions and 25 deletions
+27 -1
View File
@@ -291,7 +291,20 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primit
case '"': return visitor.visit_root_string(*this, value);
case 't': return visitor.visit_root_true_atom(*this, value);
case 'f': return visitor.visit_root_false_atom(*this, value);
#if SIMDJSON_ENABLE_NAN_INF
case 'n': {
auto err = visitor.visit_root_null_atom(*this, value);
if (err == SUCCESS) { return err; }
// propagate the error value returned by a bad 'null' atom if parsing 'nan' fails
return visitor.visit_root_nan_atom(*this, value, err);
}
// 'N' isn't a canonically recognized atom, so we return a TAPE_ERROR if failure occurs
case 'N': return visitor.visit_root_nan_atom(*this, value, TAPE_ERROR);
case 'i':
case 'I': return visitor.visit_root_inf_atom(*this, value);
#else
case 'n': return visitor.visit_root_null_atom(*this, value);
#endif
case '-':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
@@ -313,7 +326,20 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V
switch (*value) {
case 't': return visitor.visit_true_atom(*this, value);
case 'f': return visitor.visit_false_atom(*this, value);
#if SIMDJSON_ENABLE_NAN_INF
case 'n': {
auto err = visitor.visit_null_atom(*this, value);
if (err == SUCCESS) { return err; }
// propagate the error value returned by a bad 'null' atom if parsing 'nan' fails
return visitor.visit_nan_atom(*this, value, err);
}
// 'N' isn't a canonically recognized atom, so we return a TAPE_ERROR if failure occurs
case 'N': return visitor.visit_nan_atom(*this, value, TAPE_ERROR);
case 'i':
case 'I': return visitor.visit_inf_atom(*this, value);
#else
case 'n': return visitor.visit_null_atom(*this, value);
#endif
default:
log_error("Non-value found when value was expected!");
return TAPE_ERROR;
@@ -325,4 +351,4 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H
#endif // SIMDJSON_SRC_GENERIC_STAGE2_JSON_ITERATOR_H
+42 -1
View File
@@ -76,6 +76,15 @@ struct tape_builder {
simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept;
#if SIMDJSON_ENABLE_NAN_INF
simdjson_warn_unused simdjson_inline error_code visit_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept;
// Attempts to parse 'inf' or 'infinity' (case insensitive). Because neither are canonical atoms,
// this returns a tape error on failure.
simdjson_warn_unused simdjson_inline error_code visit_inf_atom(json_iterator &iter, const uint8_t *value) noexcept;
simdjson_warn_unused simdjson_inline error_code visit_root_inf_atom(json_iterator &iter, const uint8_t *value) noexcept;
#endif
/** Called each time a new field or element in an array or object is found. */
simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept;
@@ -255,6 +264,38 @@ simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_at
return SUCCESS;
}
#if SIMDJSON_ENABLE_NAN_INF
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept {
iter.log_value("nan");
if (!atomparsing::is_valid_nan_atom(value)) { return errc; }
tape.append_double(std::numeric_limits<double>::quiet_NaN());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_nan_atom(json_iterator &iter, const uint8_t *value, error_code errc) noexcept {
iter.log_value("nan");
if (!atomparsing::is_valid_nan_atom(value, iter.remaining_len())) { return errc; }
tape.append_double(std::numeric_limits<double>::quiet_NaN());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_inf_atom(json_iterator &iter, const uint8_t *value) noexcept {
iter.log_value("inf");
// Because 'inf' is an extension, non a canonical atom, a tape error should be returned on failure
if (!atomparsing::is_valid_inf_atom(value)) { return TAPE_ERROR; }
tape.append_double(std::numeric_limits<double>::infinity());
return SUCCESS;
}
simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_inf_atom(json_iterator &iter, const uint8_t *value) noexcept {
iter.log_value("inf");
// Because 'inf' is an extension, non a canonical atom, a tape error should be returned on failure
if (!atomparsing::is_valid_inf_atom(value, iter.remaining_len())) { return TAPE_ERROR; }
tape.append_double(std::numeric_limits<double>::infinity());
return SUCCESS;
}
#endif // SIMDJSON_ENABLE_NAN_INF
// private:
simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept {
@@ -310,4 +351,4 @@ simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept {
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H
#endif // SIMDJSON_SRC_GENERIC_STAGE2_TAPE_BUILDER_H