From d2cfc25e20af8a3b39cfae96845e0f5f2164743f Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Mon, 2 Sep 2024 18:06:40 -0400 Subject: [PATCH] Compile out assertions that are obviously invalid Signed-off-by: Juan Cruz Viotti --- src/jsonschema/default_compiler_2019_09.h | 31 ++++- src/jsonschema/default_compiler_draft4.h | 131 ++++++++++++++++++ src/jsonschema/default_compiler_draft6.h | 28 ++++ .../jsonschema_compile_draft4_test.cc | 50 +++++++ 4 files changed, 239 insertions(+), 1 deletion(-) diff --git a/src/jsonschema/default_compiler_2019_09.h b/src/jsonschema/default_compiler_2019_09.h index c14f1db3d..c0f314255 100644 --- a/src/jsonschema/default_compiler_2019_09.h +++ b/src/jsonschema/default_compiler_2019_09.h @@ -16,8 +16,14 @@ auto compiler_2019_09_applicator_dependentschemas( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_object()); - SchemaCompilerTemplate children; + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + SchemaCompilerTemplate children; for (const auto &entry : schema_context.schema.at(dynamic_context.keyword).as_object()) { if (!is_schema(entry.second)) { @@ -48,6 +54,12 @@ auto compiler_2019_09_validation_dependentrequired( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + SchemaCompilerValueStringMap dependencies; for (const auto &entry : schema_context.schema.at(dynamic_context.keyword).as_object()) { @@ -90,6 +102,11 @@ auto compiler_2019_09_applicator_contains_conditional_annotate( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } std::size_t minimum{1}; if (schema_context.schema.defines("minContains")) { @@ -192,6 +209,12 @@ auto compiler_2019_09_applicator_unevaluateditems( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + SchemaCompilerTemplate children{compile(context, schema_context, relative_dynamic_context, empty_pointer, empty_pointer)}; @@ -227,6 +250,12 @@ auto compiler_2019_09_applicator_unevaluatedproperties( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + SchemaCompilerValueStrings dependencies{"unevaluatedProperties"}; if (schema_context.vocabularies.contains( diff --git a/src/jsonschema/default_compiler_draft4.h b/src/jsonschema/default_compiler_draft4.h index d68a42ae1..8b5bc36d2 100644 --- a/src/jsonschema/default_compiler_draft4.h +++ b/src/jsonschema/default_compiler_draft4.h @@ -181,6 +181,12 @@ auto compiler_draft4_validation_required( -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_array()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + if (schema_context.schema.at(dynamic_context.keyword).empty()) { return {}; } else if (schema_context.schema.at(dynamic_context.keyword).size() > 1) { @@ -308,6 +314,12 @@ auto compiler_draft4_applicator_properties( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + const auto loads_unevaluated_keywords = schema_context.vocabularies.contains( "https://json-schema.org/draft/2019-09/vocab/applicator") || @@ -430,6 +442,12 @@ auto compiler_draft4_applicator_patternproperties( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + const auto loads_unevaluated_keywords = schema_context.vocabularies.contains( "https://json-schema.org/draft/2019-09/vocab/applicator") || @@ -477,6 +495,12 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + SchemaCompilerTemplate children{compile(context, schema_context, relative_dynamic_context, empty_pointer, empty_pointer)}; @@ -523,6 +547,13 @@ auto compiler_draft4_validation_pattern( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_string()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + const auto ®ex_string{ schema_context.schema.at(dynamic_context.keyword).to_string()}; return {make( @@ -541,6 +572,12 @@ auto compiler_draft4_validation_format( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + // Regular expressions static const std::string FORMAT_REGEX_IPV4{ @@ -598,6 +635,12 @@ auto compiler_draft4_applicator_items_array( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + // The idea here is to precompile all possibilities depending on the size // of the instance array up to the size of the `items` keyword array. // For example, if `items` is set to `[ {}, {}, {} ]`, we create 3 @@ -668,6 +711,12 @@ auto compiler_draft4_applicator_items_conditional_annotation( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + if (is_schema(schema_context.schema.at(dynamic_context.keyword))) { if (annotate) { SchemaCompilerTemplate children; @@ -712,6 +761,12 @@ auto compiler_draft4_applicator_additionalitems_from_cursor( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, const std::size_t cursor, const bool annotate) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + SchemaCompilerTemplate children{make( true, context, schema_context, relative_dynamic_context, SchemaCompilerValueUnsignedInteger{cursor}, @@ -736,6 +791,12 @@ auto compiler_draft4_applicator_additionalitems_conditional_annotation( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context, const bool annotate) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + assert(schema_context.schema.is_object()); // Nothing to do here @@ -767,6 +828,12 @@ auto compiler_draft4_applicator_dependencies( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + assert(schema_context.schema.at(dynamic_context.keyword).is_object()); SchemaCompilerTemplate children; SchemaCompilerValueStringMap dependencies; @@ -840,6 +907,12 @@ auto compiler_draft4_validation_uniqueitems( return {}; } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueNone{}, SchemaCompilerTemplate{})}; @@ -854,6 +927,12 @@ auto compiler_draft4_validation_maxlength( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + // TODO: As an optimization, if `minLength` is set to the same number, do // a single size equality assertion return {make( @@ -876,6 +955,12 @@ auto compiler_draft4_validation_minlength( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + // TODO: As an optimization, if `maxLength` is set to the same number, do // a single size equality assertion return {make( @@ -898,6 +983,12 @@ auto compiler_draft4_validation_maxitems( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + // TODO: As an optimization, if `minItems` is set to the same number, do // a single size equality assertion return {make( @@ -920,6 +1011,12 @@ auto compiler_draft4_validation_minitems( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + // TODO: As an optimization, if `maxItems` is set to the same number, do // a single size equality assertion return {make( @@ -942,6 +1039,12 @@ auto compiler_draft4_validation_maxproperties( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + // TODO: As an optimization, if `minProperties` is set to the same number, do // a single size equality assertion return {make( @@ -964,6 +1067,12 @@ auto compiler_draft4_validation_minproperties( schema_context.schema.at(dynamic_context.keyword).is_integer_real()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + // TODO: As an optimization, if `maxProperties` is set to the same number, do // a single size equality assertion return {make( @@ -984,6 +1093,13 @@ auto compiler_draft4_validation_maximum( -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_number()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + // TODO: As an optimization, if `minimum` is set to the same number, do // a single equality assertion @@ -1010,6 +1126,13 @@ auto compiler_draft4_validation_minimum( -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_number()); + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + // TODO: As an optimization, if `maximum` is set to the same number, do // a single equality assertion @@ -1036,6 +1159,14 @@ auto compiler_draft4_validation_multipleof( -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_number()); assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, JSON{schema_context.schema.at(dynamic_context.keyword)}, diff --git a/src/jsonschema/default_compiler_draft6.h b/src/jsonschema/default_compiler_draft6.h index f4233e12b..645379f00 100644 --- a/src/jsonschema/default_compiler_draft6.h +++ b/src/jsonschema/default_compiler_draft6.h @@ -135,6 +135,14 @@ auto compiler_draft6_validation_exclusivemaximum( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_number()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, JSON{schema_context.schema.at(dynamic_context.keyword)}, @@ -147,6 +155,14 @@ auto compiler_draft6_validation_exclusiveminimum( const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { assert(schema_context.schema.at(dynamic_context.keyword).is_number()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, JSON{schema_context.schema.at(dynamic_context.keyword)}, @@ -158,6 +174,12 @@ auto compiler_draft6_applicator_contains( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueRange{1, std::nullopt, false}, @@ -171,6 +193,12 @@ auto compiler_draft6_validation_propertynames( const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + return {make( true, context, schema_context, dynamic_context, SchemaCompilerValueNone{}, compile(context, schema_context, relative_dynamic_context, empty_pointer, diff --git a/test/jsonschema/jsonschema_compile_draft4_test.cc b/test/jsonschema/jsonschema_compile_draft4_test.cc index 3476ca55d..9cb79c9e5 100644 --- a/test/jsonschema/jsonschema_compile_draft4_test.cc +++ b/test/jsonschema/jsonschema_compile_draft4_test.cc @@ -3474,6 +3474,31 @@ TEST(JSONSchema_compile_draft4, minItems_3) { "least 2 items but it contained 1 item"); } +TEST(JSONSchema_compile_draft4, minItems_4) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "minItems": 2 + })JSON")}; + + const auto compiled_schema{sourcemeta::jsontoolkit::compile( + schema, sourcemeta::jsontoolkit::default_schema_walker, + sourcemeta::jsontoolkit::official_resolver, + sourcemeta::jsontoolkit::default_schema_compiler, + sourcemeta::jsontoolkit::SchemaCompilerCompilationMode::Full)}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("{}")}; + EVALUATE_WITH_TRACE_FAST_SUCCESS(compiled_schema, instance, 1); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type object"); +} + TEST(JSONSchema_compile_draft4, maxItems_1) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({ @@ -3543,6 +3568,31 @@ TEST(JSONSchema_compile_draft4, maxItems_3) { "most 2 items but it contained 3 items"); } +TEST(JSONSchema_compile_draft4, maxItems_4) { + const sourcemeta::jsontoolkit::JSON schema{ + sourcemeta::jsontoolkit::parse(R"JSON({ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "maxItems": 2 + })JSON")}; + + const auto compiled_schema{sourcemeta::jsontoolkit::compile( + schema, sourcemeta::jsontoolkit::default_schema_walker, + sourcemeta::jsontoolkit::official_resolver, + sourcemeta::jsontoolkit::default_schema_compiler, + sourcemeta::jsontoolkit::SchemaCompilerCompilationMode::Full)}; + + const sourcemeta::jsontoolkit::JSON instance{ + sourcemeta::jsontoolkit::parse("{}")}; + EVALUATE_WITH_TRACE_FAST_SUCCESS(compiled_schema, instance, 1); + + EVALUATE_TRACE_PRE(0, AssertionTypeStrict, "/type", "#/type", ""); + EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/type", "#/type", ""); + + EVALUATE_TRACE_POST_DESCRIBE(instance, 0, + "The value was expected to be of type object"); +} + TEST(JSONSchema_compile_draft4, minProperties_1) { const sourcemeta::jsontoolkit::JSON schema{ sourcemeta::jsontoolkit::parse(R"JSON({