diff --git a/DEPENDENCIES b/DEPENDENCIES index 8949c99..e5a71de 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -4,4 +4,4 @@ jsontoolkit https://github.com/sourcemeta/jsontoolkit 7a398224cc2e76ea9ae8541a87 hydra https://github.com/sourcemeta/hydra a4a74f3cabd32f2f829f449d67339dac33f9910e alterschema https://github.com/sourcemeta/alterschema 92e370ce9c1f0582014b54d43e388ee012dfe13d jsonbinpack https://github.com/sourcemeta/jsonbinpack d777179441d3c703e1fda1187742541aa26836b5 -blaze https://github.com/sourcemeta/blaze a7885ab63fcbb4f8d974e574a34bd9c5ccf31a2c +blaze https://github.com/sourcemeta/blaze cf0c89cd419ffb70cc334d395ac5ab1035702e30 diff --git a/src/command_metaschema.cc b/src/command_metaschema.cc index fb6c53f..c2e80a7 100644 --- a/src/command_metaschema.cc +++ b/src/command_metaschema.cc @@ -44,7 +44,7 @@ auto sourcemeta::jsonschema::cli::metaschema( cache.insert({dialect.value(), metaschema_template}); } - sourcemeta::blaze::ErrorTraceOutput output{entry.second}; + sourcemeta::blaze::ErrorOutput output{entry.second}; if (sourcemeta::blaze::evaluate(cache.at(dialect.value()), entry.second, std::ref(output))) { log_verbose(options) diff --git a/src/command_test.cc b/src/command_test.cc index 96feaba..7f74e7e 100644 --- a/src/command_test.cc +++ b/src/command_test.cc @@ -244,7 +244,7 @@ auto sourcemeta::jsonschema::cli::test( const auto instance{ get_data(test_case, entry.first.parent_path(), verbose)}; const std::string ref{"$ref"}; - sourcemeta::blaze::ErrorTraceOutput output{instance, {std::cref(ref)}}; + sourcemeta::blaze::ErrorOutput output{instance, {std::cref(ref)}}; const auto case_result{sourcemeta::blaze::evaluate( schema_template, instance, std::ref(output))}; diff --git a/src/command_validate.cc b/src/command_validate.cc index 3001370..b22449c 100644 --- a/src/command_validate.cc +++ b/src/command_validate.cc @@ -72,7 +72,7 @@ auto sourcemeta::jsonschema::cli::validate( for (const auto &instance : sourcemeta::jsontoolkit::JSONL{stream}) { index += 1; std::ostringstream error; - sourcemeta::blaze::ErrorTraceOutput output{instance}; + sourcemeta::blaze::ErrorOutput output{instance}; bool subresult = true; if (benchmark) { const auto timestamp_start{ @@ -124,7 +124,7 @@ auto sourcemeta::jsonschema::cli::validate( } else { const auto instance{sourcemeta::jsontoolkit::from_file(instance_path)}; std::ostringstream error; - sourcemeta::blaze::ErrorTraceOutput output{instance}; + sourcemeta::blaze::ErrorOutput output{instance}; bool subresult{true}; if (benchmark) { const auto timestamp_start{std::chrono::high_resolution_clock::now()}; diff --git a/src/utils.cc b/src/utils.cc index 865dd46..b03b584 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -175,8 +175,8 @@ auto parse_options(const std::span &arguments, return options; } -auto print(const sourcemeta::blaze::ErrorTraceOutput &output, - std::ostream &stream) -> void { +auto print(const sourcemeta::blaze::ErrorOutput &output, std::ostream &stream) + -> void { stream << "error: Schema validation failure\n"; for (const auto &entry : output) { stream << " " << entry.message << "\n"; diff --git a/src/utils.h b/src/utils.h index 27fda5a..9103c2c 100644 --- a/src/utils.h +++ b/src/utils.h @@ -29,8 +29,8 @@ auto for_each_json(const std::vector &arguments, -> std::vector< std::pair>; -auto print(const sourcemeta::blaze::ErrorTraceOutput &output, - std::ostream &stream) -> void; +auto print(const sourcemeta::blaze::ErrorOutput &output, std::ostream &stream) + -> void; auto resolver(const std::map> &options, const bool remote = false) diff --git a/vendor/blaze/src/compiler/compile_describe.cc b/vendor/blaze/src/compiler/compile_describe.cc index 2d36132..42da013 100644 --- a/vendor/blaze/src/compiler/compile_describe.cc +++ b/vendor/blaze/src/compiler/compile_describe.cc @@ -657,6 +657,11 @@ struct DescribeVisitor { return message.str(); } + auto operator()(const LoopPropertiesWhitelist &) const -> std::string { + assert(this->keyword == "additionalProperties"); + return "The object value was not expected to define additional properties"; + } + auto operator()(const LoopPropertiesType &step) const -> std::string { std::ostringstream message; message << "The object properties were expected to be of type " diff --git a/vendor/blaze/src/compiler/compile_helpers.h b/vendor/blaze/src/compiler/compile_helpers.h index 24065b6..2f0459d 100644 --- a/vendor/blaze/src/compiler/compile_helpers.h +++ b/vendor/blaze/src/compiler/compile_helpers.h @@ -76,15 +76,11 @@ auto make(const Context &context, const SchemaContext &schema_context, } template -auto unroll(const DynamicContext &dynamic_context, const Step &step, +auto unroll(const Step &step, const sourcemeta::jsontoolkit::Pointer &base_instance_location = sourcemeta::jsontoolkit::empty_pointer) -> Type { assert(std::holds_alternative(step)); - return {dynamic_context.keyword.empty() - ? std::get(step).relative_schema_location - : dynamic_context.base_schema_location - .concat({dynamic_context.keyword}) - .concat(std::get(step).relative_schema_location), + return {std::get(step).relative_schema_location, base_instance_location.concat( std::get(step).relative_instance_location), std::get(step).keyword_location, @@ -94,6 +90,17 @@ auto unroll(const DynamicContext &dynamic_context, const Step &step, std::get(step).value}; } +template +auto rephrase(const Step &step) -> Type { + return {step.relative_schema_location, + step.relative_instance_location, + step.keyword_location, + step.schema_resource, + step.dynamic, + step.track, + step.value}; +} + inline auto unsigned_integer_property(const sourcemeta::jsontoolkit::JSON &document, const sourcemeta::jsontoolkit::JSON::String &property) diff --git a/vendor/blaze/src/compiler/compile_json.cc b/vendor/blaze/src/compiler/compile_json.cc index da67c76..3bc9ee3 100644 --- a/vendor/blaze/src/compiler/compile_json.cc +++ b/vendor/blaze/src/compiler/compile_json.cc @@ -274,6 +274,7 @@ struct StepVisitor { HANDLE_STEP("loop", "properties-regex", LoopPropertiesRegex) HANDLE_STEP("loop", "properties-starts-with", LoopPropertiesStartsWith) HANDLE_STEP("loop", "properties-except", LoopPropertiesExcept) + HANDLE_STEP("loop", "properties-whitelist", LoopPropertiesWhitelist) HANDLE_STEP("loop", "properties-type", LoopPropertiesType) HANDLE_STEP("loop", "properties-type-evaluate", LoopPropertiesTypeEvaluate) HANDLE_STEP("loop", "properties-type-strict", LoopPropertiesTypeStrict) diff --git a/vendor/blaze/src/compiler/compile_output.cc b/vendor/blaze/src/compiler/compile_output.cc index e3612c8..bc09221 100644 --- a/vendor/blaze/src/compiler/compile_output.cc +++ b/vendor/blaze/src/compiler/compile_output.cc @@ -9,28 +9,23 @@ namespace sourcemeta::blaze { -ErrorTraceOutput::ErrorTraceOutput( - const sourcemeta::jsontoolkit::JSON &instance, - const sourcemeta::jsontoolkit::WeakPointer &base) +ErrorOutput::ErrorOutput(const sourcemeta::jsontoolkit::JSON &instance, + const sourcemeta::jsontoolkit::WeakPointer &base) : instance_{instance}, base_{base} {} -auto ErrorTraceOutput::begin() const -> const_iterator { +auto ErrorOutput::begin() const -> const_iterator { return this->output.begin(); } -auto ErrorTraceOutput::end() const -> const_iterator { - return this->output.end(); -} +auto ErrorOutput::end() const -> const_iterator { return this->output.end(); } -auto ErrorTraceOutput::cbegin() const -> const_iterator { +auto ErrorOutput::cbegin() const -> const_iterator { return this->output.cbegin(); } -auto ErrorTraceOutput::cend() const -> const_iterator { - return this->output.cend(); -} +auto ErrorOutput::cend() const -> const_iterator { return this->output.cend(); } -auto ErrorTraceOutput::operator()( +auto ErrorOutput::operator()( const EvaluationType type, const bool result, const Template::value_type &step, const sourcemeta::jsontoolkit::WeakPointer &evaluate_path, diff --git a/vendor/blaze/src/compiler/default_compiler_draft4.h b/vendor/blaze/src/compiler/default_compiler_draft4.h index 0378425..9937b0d 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft4.h +++ b/vendor/blaze/src/compiler/default_compiler_draft4.h @@ -4,12 +4,12 @@ #include #include -#include // std::sort, std::any_of, std::all_of -#include // assert -#include // std::regex, std::regex_error -#include // std::set -#include // std::ostringstream -#include // std::move +#include // std::sort, std::any_of, std::all_of, std::find_if, std::none_of +#include // assert +#include // std::regex, std::regex_error +#include // std::set +#include // std::ostringstream +#include // std::move #include "compile_helpers.h" @@ -43,12 +43,28 @@ static auto collect_jump_labels(const sourcemeta::blaze::Template &steps, } } +static auto relative_schema_location_size( + const sourcemeta::blaze::Template::value_type &variant) -> std::size_t { + return std::visit( + [](const auto &step) { return step.relative_schema_location.size(); }, + variant); +} + static auto defines_direct_enumeration(const sourcemeta::blaze::Template &steps) - -> bool { - return std::any_of(steps.cbegin(), steps.cend(), [](const auto &step) { - return std::holds_alternative(step) || - std::holds_alternative(step); - }); + -> std::optional { + const auto iterator{ + std::find_if(steps.cbegin(), steps.cend(), [](const auto &step) { + return std::holds_alternative( + step) || + std::holds_alternative( + step); + })}; + + if (iterator == steps.cend()) { + return std::nullopt; + } + + return std::distance(steps.cbegin(), iterator); } static auto @@ -487,6 +503,55 @@ auto compiler_draft4_applicator_oneof(const Context &context, std::move(disjunctors))}; } +static auto compile_properties(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context) + -> std::vector> { + std::vector> properties; + for (const auto &entry : schema_context.schema.at("properties").as_object()) { + properties.push_back( + {entry.first, compile(context, schema_context, dynamic_context, + {entry.first}, {entry.first})}); + } + + // In many cases, `properties` have some subschemas that are small + // and some subschemas that are large. To attempt to improve performance, + // we prefer to evaluate smaller subschemas first, in the hope of failing + // earlier without spending a lot of time on other subschemas + std::sort(properties.begin(), properties.end(), + [](const auto &left, const auto &right) { + const auto left_size{recursive_template_size(left.second)}; + const auto right_size{recursive_template_size(right.second)}; + if (left_size == right_size) { + const auto left_direct_enumeration{ + defines_direct_enumeration(left.second)}; + const auto right_direct_enumeration{ + defines_direct_enumeration(right.second)}; + + // Enumerations always take precedence + if (left_direct_enumeration.has_value() && + right_direct_enumeration.has_value()) { + // If both options have a direct enumeration, we choose + // the one with the shorter relative schema location + return relative_schema_location_size( + left.second.at(left_direct_enumeration.value())) < + relative_schema_location_size( + right.second.at(right_direct_enumeration.value())); + } else if (left_direct_enumeration.has_value()) { + return true; + } else if (right_direct_enumeration.has_value()) { + return false; + } + + return left.first < right.first; + } else { + return left_size < right_size; + } + }); + + return properties; +} + auto compiler_draft4_applicator_properties_with_options( const Context &context, const SchemaContext &schema_context, const DynamicContext &dynamic_context, const bool annotate, @@ -514,6 +579,15 @@ auto compiler_draft4_applicator_properties_with_options( "https://json-schema.org/draft/2019-09/vocab/validation") || schema_context.vocabularies.contains( "https://json-schema.org/draft/2020-12/vocab/validation"); + const auto imports_const = + schema_context.vocabularies.contains( + "http://json-schema.org/draft-06/schema#") || + schema_context.vocabularies.contains( + "http://json-schema.org/draft-07/schema#") || + schema_context.vocabularies.contains( + "https://json-schema.org/draft/2019-09/vocab/validation") || + schema_context.vocabularies.contains( + "https://json-schema.org/draft/2020-12/vocab/validation"); std::set required; if (imports_validation_vocabulary && schema_context.schema.defines("required") && @@ -529,40 +603,6 @@ auto compiler_draft4_applicator_properties_with_options( } } - std::size_t is_required = 0; - std::vector> properties; - for (const auto &entry : - schema_context.schema.at(dynamic_context.keyword).as_object()) { - properties.push_back( - {entry.first, compile(context, schema_context, relative_dynamic_context, - {entry.first}, {entry.first})}); - if (required.contains(entry.first)) { - is_required += 1; - } - } - - // In many cases, `properties` have some subschemas that are small - // and some subschemas that are large. To attempt to improve performance, - // we prefer to evaluate smaller subschemas first, in the hope of failing - // earlier without spending a lot of time on other subschemas - std::sort(properties.begin(), properties.end(), - [](const auto &left, const auto &right) { - // Enumerations always take precedence - if (defines_direct_enumeration(left.second)) { - return true; - } else if (defines_direct_enumeration(right.second)) { - return false; - } - - const auto left_size{recursive_template_size(left.second)}; - const auto right_size{recursive_template_size(right.second)}; - if (left_size == right_size) { - return left.first < right.first; - } else { - return left_size < right_size; - } - }); - const auto ¤t_entry{static_frame_entry(context, schema_context)}; const auto inside_disjunctor{ is_inside_disjunctor(schema_context.relative_pointer) || @@ -590,23 +630,32 @@ auto compiler_draft4_applicator_properties_with_options( // in the corresponding case. const auto prefer_loop_over_instance{ // This strategy only makes sense if most of the properties are "optional" - is_required <= (size / 4) && + required.size() <= (size / 4) && // If `properties` only defines a relatively small amount of properties, // then its probably still faster to unroll size > 5 && - // Unless the properties definition has a LOT of optional properties, - // we should unroll inside `oneOf` or `anyOf`, to have a better chance at - // short-circuiting quickly - // TODO: Maybe the proper middle ground here is to ONLY - // unroll properties with `const`/`enum`? - (!inside_disjunctor || (is_required == 0 && size > 20))}; + // Always unroll inside `oneOf` or `anyOf`, to have a + // better chance at quickly short-circuiting + (!inside_disjunctor || + std::none_of( + schema_context.schema.at(dynamic_context.keyword) + .as_object() + .cbegin(), + schema_context.schema.at(dynamic_context.keyword).as_object().cend(), + [&](const auto &pair) { + return pair.second.is_object() && + ((imports_validation_vocabulary && + pair.second.defines("enum")) || + (imports_const && pair.second.defines("const"))); + }))}; if (prefer_loop_over_instance) { ValueNamedIndexes indexes; Template children; std::size_t cursor = 0; - for (auto &&[name, substeps] : properties) { + for (auto &&[name, substeps] : compile_properties( + context, schema_context, relative_dynamic_context)) { indexes.emplace(name, cursor); if (track_evaluation) { @@ -634,19 +683,18 @@ auto compiler_draft4_applicator_properties_with_options( Template children; - for (auto &&[name, substeps] : properties) { + const auto effective_dynamic_context{context.mode == Mode::FastValidation + ? dynamic_context + : relative_dynamic_context}; + + for (auto &&[name, substeps] : + compile_properties(context, schema_context, effective_dynamic_context)) { if (annotate) { substeps.push_back(make( - context, schema_context, relative_dynamic_context, + context, schema_context, effective_dynamic_context, sourcemeta::jsontoolkit::JSON{name})); } - const auto assume_object{imports_validation_vocabulary && - schema_context.schema.defines("type") && - schema_context.schema.at("type").is_string() && - schema_context.schema.at("type").to_string() == - "object"}; - // Optimize `properties` where its subschemas just include a type check, // as that's a very common pattern @@ -654,131 +702,87 @@ auto compiler_draft4_applicator_properties_with_options( std::holds_alternative(substeps.front())) { const auto &type_step{std::get(substeps.front())}; if (track_evaluation) { - children.push_back(AssertionPropertyTypeStrictEvaluate{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back( + rephrase(type_step)); } else { - children.push_back(AssertionPropertyTypeStrict{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back(rephrase(type_step)); } } else if (context.mode == Mode::FastValidation && substeps.size() == 1 && std::holds_alternative(substeps.front())) { const auto &type_step{std::get(substeps.front())}; if (track_evaluation) { - children.push_back(AssertionPropertyTypeEvaluate{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back(rephrase(type_step)); } else { - children.push_back(AssertionPropertyType{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back(rephrase(type_step)); } } else if (context.mode == Mode::FastValidation && substeps.size() == 1 && std::holds_alternative( substeps.front())) { const auto &type_step{std::get(substeps.front())}; if (track_evaluation) { - children.push_back(AssertionPropertyTypeStrictAnyEvaluate{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back( + rephrase(type_step)); } else { - children.push_back(AssertionPropertyTypeStrictAny{ - type_step.relative_schema_location, - dynamic_context.base_instance_location.concat( - type_step.relative_instance_location), - type_step.keyword_location, type_step.schema_resource, - type_step.dynamic, type_step.track, type_step.value}); + children.push_back(rephrase(type_step)); } } else if (context.mode == Mode::FastValidation && substeps.size() == 1 && std::holds_alternative( substeps.front())) { children.push_back(unroll( - relative_dynamic_context, substeps.front(), - dynamic_context.base_instance_location)); + substeps.front(), effective_dynamic_context.base_instance_location)); } else if (context.mode == Mode::FastValidation && substeps.size() == 1 && std::holds_alternative( substeps.front())) { children.push_back(unroll( - relative_dynamic_context, substeps.front(), - dynamic_context.base_instance_location)); + substeps.front(), effective_dynamic_context.base_instance_location)); } else if (context.mode == Mode::FastValidation && substeps.size() == 1 && std::holds_alternative( substeps.front())) { children.push_back(unroll( - relative_dynamic_context, substeps.front(), - dynamic_context.base_instance_location)); + substeps.front(), effective_dynamic_context.base_instance_location)); } else { if (track_evaluation) { - substeps.push_back(make(context, schema_context, - relative_dynamic_context, - ValuePointer{name})); + if (context.mode == Mode::FastValidation) { + // We need this wrapper as `ControlEvaluate` doesn't push to the stack + substeps.push_back(make( + context, schema_context, effective_dynamic_context, ValueNone{}, + {make(context, schema_context, + effective_dynamic_context, + ValuePointer{name})})); + } else { + substeps.push_back(make(context, schema_context, + effective_dynamic_context, + ValuePointer{name})); + } } - if (imports_validation_vocabulary && assume_object && - schema_context.schema.defines("required") && - schema_context.schema.at("required").is_array() && - schema_context.schema.at("required") - .contains(sourcemeta::jsontoolkit::JSON{name})) { + if (imports_validation_vocabulary && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object" && + required.contains(name)) { // We can avoid the container too and just inline these steps for (auto &&substep : substeps) { children.push_back(std::move(substep)); } } else if (!substeps.empty()) { children.push_back(make( - context, schema_context, relative_dynamic_context, + context, schema_context, effective_dynamic_context, ValueString{name}, std::move(substeps))); } } } - // Optimize away the wrapper when emitting a single instruction - if (context.mode == Mode::FastValidation && children.size() == 1 && - std::holds_alternative(children.front())) { - return { - unroll(dynamic_context, children.front())}; - } else if (context.mode == Mode::FastValidation && children.size() == 1 && - std::holds_alternative( - children.front())) { - return {unroll(dynamic_context, - children.front())}; - } else if (context.mode == Mode::FastValidation && children.size() == 1 && - std::holds_alternative( - children.front())) { - return {unroll(dynamic_context, - children.front())}; - } else if (context.mode == Mode::FastValidation && children.size() == 1 && - std::holds_alternative( - children.front())) { - return {unroll(dynamic_context, - children.front())}; - } - - if (children.empty()) { + if (context.mode == Mode::FastValidation) { + return children; + } else if (children.empty()) { return {}; + } else { + return {make(context, schema_context, dynamic_context, + ValueNone{}, std::move(children))}; } - - // TODO: Should this be LogicalWhenType? If so, we can avoid evaluating - // every unrolled property against non-object instances - return {make(context, schema_context, dynamic_context, - ValueNone{}, std::move(children))}; } auto compiler_draft4_applicator_properties( @@ -912,6 +916,14 @@ auto compiler_draft4_applicator_additionalproperties_with_options( return {}; } + if (context.mode == Mode::FastValidation && children.size() == 1 && + std::holds_alternative(children.front()) && + !filter_strings.empty() && filter_prefixes.empty() && + filter_regexes.empty()) { + return {make( + context, schema_context, dynamic_context, std::move(filter_strings))}; + } + if (!filter_strings.empty() || !filter_prefixes.empty() || !filter_regexes.empty()) { if (track_evaluation) { diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_output.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_output.h index 8fc27a0..cfd3fa4 100644 --- a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_output.h +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_output.h @@ -44,7 +44,7 @@ namespace sourcemeta::blaze { /// /// const sourcemeta::jsontoolkit::JSON instance{5}; /// -/// sourcemeta::blaze::ErrorTraceOutput output; +/// sourcemeta::blaze::ErrorOutput output; /// const auto result{sourcemeta::blaze::evaluate( /// schema_template, instance, std::ref(output))}; /// @@ -58,15 +58,15 @@ namespace sourcemeta::blaze { /// } /// } /// ``` -class SOURCEMETA_BLAZE_COMPILER_EXPORT ErrorTraceOutput { +class SOURCEMETA_BLAZE_COMPILER_EXPORT ErrorOutput { public: - ErrorTraceOutput(const sourcemeta::jsontoolkit::JSON &instance, - const sourcemeta::jsontoolkit::WeakPointer &base = - sourcemeta::jsontoolkit::empty_weak_pointer); + ErrorOutput(const sourcemeta::jsontoolkit::JSON &instance, + const sourcemeta::jsontoolkit::WeakPointer &base = + sourcemeta::jsontoolkit::empty_weak_pointer); // Prevent accidental copies - ErrorTraceOutput(const ErrorTraceOutput &) = delete; - auto operator=(const ErrorTraceOutput &) -> ErrorTraceOutput & = delete; + ErrorOutput(const ErrorOutput &) = delete; + auto operator=(const ErrorOutput &) -> ErrorOutput & = delete; struct Entry { const std::string message; diff --git a/vendor/blaze/src/evaluator/evaluator.cc b/vendor/blaze/src/evaluator/evaluator.cc index 0a54dd1..4491989 100644 --- a/vendor/blaze/src/evaluator/evaluator.cc +++ b/vendor/blaze/src/evaluator/evaluator.cc @@ -1174,6 +1174,28 @@ auto evaluate_step(const sourcemeta::blaze::Template::value_type &step, EVALUATE_END(loop, LoopPropertiesExcept); } + case IS_STEP(LoopPropertiesWhitelist): { + EVALUATE_BEGIN(loop, LoopPropertiesWhitelist, target.is_object()); + // Otherwise why emit this instruction? + assert(!loop.value.empty()); + + // Otherwise if the number of properties in the instance + // is larger than the whitelist, then it already violated + // the whitelist? + if (target.size() <= loop.value.size()) { + result = true; + for (const auto &entry : target.as_object()) { + if (std::find(loop.value.cbegin(), loop.value.cend(), entry.first) == + loop.value.cend()) { + result = false; + break; + } + } + } + + EVALUATE_END(loop, LoopPropertiesWhitelist); + } + case IS_STEP(LoopPropertiesType): { EVALUATE_BEGIN(loop, LoopPropertiesType, target.is_object()); result = true; diff --git a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_template.h b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_template.h index 96744bc..901fecc 100644 --- a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_template.h +++ b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_template.h @@ -69,6 +69,7 @@ struct LoopPropertiesEvaluate; struct LoopPropertiesRegex; struct LoopPropertiesStartsWith; struct LoopPropertiesExcept; +struct LoopPropertiesWhitelist; struct LoopPropertiesType; struct LoopPropertiesTypeEvaluate; struct LoopPropertiesTypeStrict; @@ -113,10 +114,10 @@ using Template = std::vector>; @@ -180,6 +181,7 @@ enum class TemplateIndex : std::uint8_t { LoopPropertiesRegex, LoopPropertiesStartsWith, LoopPropertiesExcept, + LoopPropertiesWhitelist, LoopPropertiesType, LoopPropertiesTypeEvaluate, LoopPropertiesTypeStrict, @@ -501,6 +503,11 @@ DEFINE_STEP_APPLICATOR(Loop, PropertiesStartsWith, ValueString) /// do not match the given property filters DEFINE_STEP_APPLICATOR(Loop, PropertiesExcept, ValuePropertyFilter) +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that fails on properties that are not part +/// of the given whitelist +DEFINE_STEP_WITH_VALUE(Loop, PropertiesWhitelist, ValueStrings) + /// @ingroup evaluator_instructions /// @brief Represents a compiler step that checks every object property is of a /// given type