From 2d2f1d772daac59ca45a2c2e7f88a996a8dd317f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Tue, 24 Feb 2026 15:44:09 -0400 Subject: [PATCH] Increase server robustness on invalid underlying data Signed-off-by: Juan Cruz Viotti --- src/server/action_jsonschema_evaluate.h | 27 ++++++++++++--- src/server/action_schema_search.h | 11 +++++-- src/shared/metapack.cc | 44 ++++++++++++++----------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/server/action_jsonschema_evaluate.h b/src/server/action_jsonschema_evaluate.h index b0a06be4..c3097629 100644 --- a/src/server/action_jsonschema_evaluate.h +++ b/src/server/action_jsonschema_evaluate.h @@ -16,6 +16,7 @@ #include // assert #include // std::filesystem::path #include // std::ostringstream +#include // std::runtime_error #include // std::string_view #include // std::underlying_type_t #include // std::move @@ -32,7 +33,9 @@ auto trace(sourcemeta::blaze::Evaluator &evaluator, auto locations_path{template_path.parent_path() / "locations.metapack"}; // TODO: Cache this across runs? const auto locations{sourcemeta::one::read_json(locations_path)}; - assert(locations.defines("static")); + if (!locations.is_object() || !locations.defines("static")) { + throw std::runtime_error("Failed to read schema locations metadata"); + } const auto &static_locations{locations.at("static")}; sourcemeta::core::PointerPositionTracker tracker; @@ -68,7 +71,9 @@ auto trace(sourcemeta::blaze::Evaluator &evaluator, // TODO: Can we avoid converting the weak pointer into a pointer // here? sourcemeta::core::to_pointer(instance_location))}; - assert(instance_positions.has_value()); + if (!instance_positions.has_value()) { + throw std::runtime_error("Failed to resolve instance positions"); + } step.assign( "instancePositions", sourcemeta::core::to_json(std::move(instance_positions).value())); @@ -88,9 +93,17 @@ auto trace(sourcemeta::blaze::Evaluator &evaluator, // Determine keyword vocabulary const auto ¤t_location{ static_locations.at(instruction.keyword_location)}; + if (!current_location.is_object() || + !current_location.defines("baseDialect") || + !current_location.defines("dialect")) { + throw std::runtime_error("Failed to resolve base dialect"); + } + const auto base_dialect_result{sourcemeta::core::to_base_dialect( current_location.at("baseDialect").to_string())}; - assert(base_dialect_result.has_value()); + if (!base_dialect_result.has_value()) { + throw std::runtime_error("Failed to resolve base dialect"); + } const auto vocabularies{sourcemeta::core::vocabularies( sourcemeta::core::schema_resolver, base_dialect_result.value(), current_location.at("dialect").to_string())}; @@ -123,14 +136,18 @@ enum class EvaluateType { Standard, Trace }; auto evaluate(const std::filesystem::path &template_path, const std::string &instance, const EvaluateType type) -> sourcemeta::core::JSON { - assert(std::filesystem::exists(template_path)); + if (!std::filesystem::exists(template_path)) { + throw std::runtime_error("Schema template not found"); + } // TODO: Cache this conversion across runs, potentially using the schema file // "checksum" as the cache key. This is important as the template might be // compressed const auto template_json{read_json(template_path)}; const auto schema_template{sourcemeta::blaze::from_json(template_json)}; - assert(schema_template.has_value()); + if (!schema_template.has_value()) { + throw std::runtime_error("Failed to parse schema template"); + } sourcemeta::blaze::Evaluator evaluator; diff --git a/src/server/action_schema_search.h b/src/server/action_schema_search.h index b601bd26..93ccbd56 100644 --- a/src/server/action_schema_search.h +++ b/src/server/action_schema_search.h @@ -13,6 +13,7 @@ #include // assert #include // std::filesystem #include // std::ostringstream +#include // std::runtime_error #include // std::string, std::getline #include // std::string_view @@ -20,10 +21,16 @@ namespace sourcemeta::one { static auto search(const std::filesystem::path &search_index, const std::string_view query) -> sourcemeta::core::JSON { - assert(std::filesystem::exists(search_index)); + if (!std::filesystem::exists(search_index)) { + throw std::runtime_error("Search index not found"); + } + assert(search_index.is_absolute()); + auto file{read_stream_raw(search_index)}; - assert(file.has_value()); + if (!file.has_value()) { + throw std::runtime_error("Failed to read search index"); + } auto result{sourcemeta::core::JSON::make_array()}; // TODO: Extend the Core JSONL iterators to be able diff --git a/src/shared/metapack.cc b/src/shared/metapack.cc index 629db8a0..fa5caebf 100644 --- a/src/shared/metapack.cc +++ b/src/shared/metapack.cc @@ -10,6 +10,7 @@ #include // std::functional #include // std::ostream #include // std::ostringstream +#include // std::runtime_error #include // std::move // TODO: There are lots of opportunities to optimise this file @@ -81,30 +82,31 @@ auto read_stream_raw(const std::filesystem::path &path) auto stream{sourcemeta::core::read_file(path)}; auto metadata{sourcemeta::core::parse_json(stream)}; - assert(metadata.is_object()); - assert(metadata.defines("version")); - assert(metadata.defines("checksum")); - assert(metadata.defines("lastModified")); - assert(metadata.defines("mime")); - assert(metadata.defines("bytes")); - assert(metadata.defines("duration")); - assert(metadata.defines("encoding")); - assert(metadata.at("version").is_integer()); - assert(metadata.at("version").is_positive()); - assert(metadata.at("checksum").is_string()); - assert(metadata.at("lastModified").is_string()); - assert(metadata.at("mime").is_string()); - assert(metadata.at("bytes").is_integer()); - assert(metadata.at("bytes").is_positive()); - assert(metadata.at("duration").is_integer()); - assert(metadata.at("duration").is_positive()); - assert(metadata.at("encoding").is_string()); + if (!metadata.is_object() || !metadata.defines("version") || + !metadata.defines("checksum") || !metadata.defines("lastModified") || + !metadata.defines("mime") || !metadata.defines("bytes") || + !metadata.defines("duration") || !metadata.defines("encoding")) { + throw std::runtime_error("The file metadata is missing required fields"); + } + + if (!metadata.at("version").is_integer() || + !metadata.at("version").is_positive() || + !metadata.at("checksum").is_string() || + !metadata.at("lastModified").is_string() || + !metadata.at("mime").is_string() || !metadata.at("bytes").is_integer() || + !metadata.at("bytes").is_positive() || + !metadata.at("duration").is_integer() || + !metadata.at("duration").is_positive() || + !metadata.at("encoding").is_string()) { + throw std::runtime_error( + "The file metadata has fields with unexpected types"); + } Encoding encoding{Encoding::Identity}; if (metadata.at("encoding").to_string() == "gzip") { encoding = Encoding::GZIP; } else if (metadata.at("encoding").to_string() != "identity") { - assert(false); + throw std::runtime_error("Failed to determine file encoding"); } return File{ @@ -134,7 +136,9 @@ auto read_json_with_metadata( const sourcemeta::core::JSON::ParseCallback &callback) -> File { auto file{read_stream_raw(path)}; - assert(file.has_value()); + if (!file.has_value()) { + throw std::runtime_error("Failed to read file"); + } std::ostringstream buffer; if (file.value().encoding == Encoding::GZIP) { sourcemeta::one::gunzip(file.value().data, buffer);