Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/alterschema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema
canonicalizer/next/implicit_array_keywords.h
canonicalizer/next/implicit_object_keywords.h
canonicalizer/next/type_with_applicator_to_allof.h
canonicalizer/next/unsatisfiable_exclusive_equal_bounds.h
canonicalizer/next/unsatisfiable_type_and_enum.h

# Common
Expand Down
3 changes: 3 additions & 0 deletions src/alterschema/alterschema.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// For built-in rules
#include <algorithm> // std::sort, std::unique, std::ranges::none_of
#include <array> // std::array
#include <bit> // std::popcount
#include <cassert> // assert
#include <cmath> // std::floor
#include <cstddef> // std::size_t
Expand Down Expand Up @@ -131,6 +132,7 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame,
#include "canonicalizer/next/implicit_array_keywords.h"
#include "canonicalizer/next/implicit_object_keywords.h"
#include "canonicalizer/next/type_with_applicator_to_allof.h"
#include "canonicalizer/next/unsatisfiable_exclusive_equal_bounds.h"
#include "canonicalizer/next/unsatisfiable_type_and_enum.h"
#include "canonicalizer/properties_implicit.h"
#include "canonicalizer/type_array_to_any_of.h"
Expand Down Expand Up @@ -239,6 +241,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void {
bundle.add<ExclusiveMinimumBooleanIntegerFold>();
bundle.add<ExclusiveMaximumBooleanIntegerFold>();
bundle.add<ExclusiveBoundsFalseDrop>();
bundle.add<UnsatisfiableExclusiveEqualBounds>();
bundle.add<ImplicitObjectKeywords>();
bundle.add<ImplicitArrayKeywords>();
}
Expand Down
3 changes: 1 addition & 2 deletions src/alterschema/canonicalizer/next/dependencies_to_any_of.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ class DependenciesToAnyOf final : public SchemaTransformRule {
}

auto transform(JSON &schema, const Result &) const -> void override {
auto result_branches{schema.defines("anyOf") ? schema.at("anyOf")
: JSON::make_array()};
auto result_branches{JSON::make_array()};

std::vector<JSON::String> processed;
for (const auto &entry : schema.at("dependencies").as_object()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class ExclusiveBoundsFalseDrop final : public SchemaTransformRule {
vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_4) &&
schema.is_object() && schema.defines("type") &&
schema.at("type").is_string() &&
schema.at("type").to_string() == "integer");
(schema.at("type").to_string() == "integer" ||
schema.at("type").to_string() == "number"));

this->has_exclusive_min_ = schema.defines("exclusiveMinimum") &&
schema.at("exclusiveMinimum").is_boolean() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,37 @@ class ExclusiveMaximumBooleanIntegerFold final : public SchemaTransformRule {
}

auto transform(JSON &schema, const Result &) const -> void override {
auto new_maximum = schema.at("maximum");
new_maximum += sourcemeta::core::JSON{-1};
schema.assign("maximum", std::move(new_maximum));
const auto &maximum{schema.at("maximum")};
if (maximum.is_integer()) {
Comment thread
jviotti marked this conversation as resolved.
Comment thread
jviotti marked this conversation as resolved.
auto new_maximum = maximum;
new_maximum += sourcemeta::core::JSON{-1};
schema.assign("maximum", std::move(new_maximum));
} else if (maximum.is_decimal()) {
auto current{maximum.to_decimal()};
auto floored{current.to_integral()};
if (floored > current) {
floored -= sourcemeta::core::Decimal{1};
}

if (current.is_integer()) {
floored -= sourcemeta::core::Decimal{1};
}

if (floored.is_int64()) {
schema.assign("maximum", sourcemeta::core::JSON{floored.to_int64()});
} else {
schema.assign("maximum", sourcemeta::core::JSON{std::move(floored)});
}
} else {
const auto value{maximum.to_real()};
auto floored{static_cast<std::int64_t>(std::floor(value))};
if (std::floor(value) == value) {
floored -= 1;
}

schema.assign("maximum", sourcemeta::core::JSON{floored});
}

schema.erase("exclusiveMaximum");
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,37 @@ class ExclusiveMinimumBooleanIntegerFold final : public SchemaTransformRule {
}

auto transform(JSON &schema, const Result &) const -> void override {
auto new_minimum = schema.at("minimum");
new_minimum += sourcemeta::core::JSON{1};
schema.assign("minimum", std::move(new_minimum));
const auto &minimum{schema.at("minimum")};
if (minimum.is_integer()) {
auto new_minimum = minimum;
new_minimum += sourcemeta::core::JSON{1};
schema.assign("minimum", std::move(new_minimum));
} else if (minimum.is_decimal()) {
auto current{minimum.to_decimal()};
auto ceiled{current.to_integral()};
if (ceiled < current) {
ceiled += sourcemeta::core::Decimal{1};
}

if (current.is_integer()) {
ceiled += sourcemeta::core::Decimal{1};
}

if (ceiled.is_int64()) {
schema.assign("minimum", sourcemeta::core::JSON{ceiled.to_int64()});
} else {
schema.assign("minimum", sourcemeta::core::JSON{std::move(ceiled)});
}
} else {
const auto value{minimum.to_real()};
auto ceiled{static_cast<std::int64_t>(std::ceil(value))};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/alterschema/canonicalizer/next/exclusive_minimum_boolean_integer_fold.h:56: static_cast<std::int64_t>(std::ceil(value)) is undefined behavior if value is NaN/Inf or outside the int64_t range, and the subsequent ceiled += 1 can also overflow. That can silently corrupt the rewritten minimum and change schema semantics for extreme/invalid numeric bounds.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

if (std::ceil(value) == value) {
ceiled += 1;
}

schema.assign("minimum", sourcemeta::core::JSON{ceiled});
}

schema.erase("exclusiveMinimum");
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ class TypeWithApplicatorToAllOf final : public SchemaTransformRule {
{allof_keyword, this->typed_branch_index_, keyword})};
return target.rebase(old_prefix, new_prefix);
} else {
const Pointer new_prefix{current.concat({allof_keyword, 1, keyword})};
const std::size_t typed_index{static_cast<std::size_t>(
std::popcount(this->applicator_indices_))};
const Pointer new_prefix{
current.concat({allof_keyword, typed_index, keyword})};
return target.rebase(old_prefix, new_prefix);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class UnsatisfiableExclusiveEqualBounds final : public SchemaTransformRule {
public:
using mutates = std::true_type;
using reframe_after_transform = std::false_type;
UnsatisfiableExclusiveEqualBounds()
: SchemaTransformRule{
"unsatisfiable_exclusive_equal_bounds",
"When `minimum` equals `maximum` and either boolean "
"`exclusiveMinimum` or `exclusiveMaximum` is true, "
"no value can satisfy the schema"} {};

[[nodiscard]] auto
condition(const sourcemeta::core::JSON &schema,
const sourcemeta::core::JSON &,
const sourcemeta::core::Vocabularies &vocabularies,
const sourcemeta::core::SchemaFrame &,
const sourcemeta::core::SchemaFrame::Location &,
const sourcemeta::core::SchemaWalker &,
const sourcemeta::core::SchemaResolver &) const
-> SchemaTransformRule::Result override {
ONLY_CONTINUE_IF(
Comment thread
jviotti marked this conversation as resolved.
vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_4) &&
schema.is_object() && schema.defines("type") &&
schema.at("type").is_string() &&
(schema.at("type").to_string() == "number" ||
schema.at("type").to_string() == "integer") &&
schema.defines("minimum") && schema.at("minimum").is_number() &&
schema.defines("maximum") && schema.at("maximum").is_number() &&
schema.at("minimum") == schema.at("maximum"));

const bool exclusive_min{schema.defines("exclusiveMinimum") &&
schema.at("exclusiveMinimum").is_boolean() &&
schema.at("exclusiveMinimum").to_boolean()};
const bool exclusive_max{schema.defines("exclusiveMaximum") &&
schema.at("exclusiveMaximum").is_boolean() &&
schema.at("exclusiveMaximum").to_boolean()};
ONLY_CONTINUE_IF(exclusive_min || exclusive_max);
return true;
}

auto transform(JSON &schema, const Result &) const -> void override {
schema.into(JSON{false});
}
};
8 changes: 7 additions & 1 deletion src/alterschema/common/equal_numeric_bounds_to_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ class EqualNumericBoundsToEnum final : public SchemaTransformRule {
schema.at("type").to_string() == "number") &&
schema.defines("minimum") && schema.at("minimum").is_number() &&
schema.defines("maximum") && schema.at("maximum").is_number() &&
schema.at("minimum") == schema.at("maximum"));
schema.at("minimum") == schema.at("maximum") &&
!(schema.defines("exclusiveMinimum") &&
schema.at("exclusiveMinimum").is_boolean() &&
schema.at("exclusiveMinimum").to_boolean()) &&
!(schema.defines("exclusiveMaximum") &&
schema.at("exclusiveMaximum").is_boolean() &&
schema.at("exclusiveMaximum").to_boolean()));
return APPLIES_TO_KEYWORDS("minimum", "maximum");
}

Expand Down
Loading
Loading