From fe18ac44ea75ee74a7a060c77a55e975c78e6df4 Mon Sep 17 00:00:00 2001 From: Maxime Arthaud Date: Thu, 8 Jun 2023 11:33:08 -0700 Subject: [PATCH] Raise an error on invalid members in json Summary: This will help us catch typos when writing model generators or rules. Reviewed By: anwesht Differential Revision: D46559562 fbshipit-source-id: a992690b03ebe580192fc6f515c33390e9ad725e --- source/FeatureMayAlwaysSet.cpp | 8 +- source/FeatureMayAlwaysSet.h | 3 +- source/FieldModel.cpp | 3 + source/JsonValidation.cpp | 26 ++++++ source/JsonValidation.h | 6 ++ source/Kind.cpp | 8 +- source/Kind.h | 5 +- source/Method.cpp | 5 ++ source/Model.cpp | 32 +++++++- source/Model.h | 3 +- source/MultiSourceMultiSinkRule.cpp | 3 + source/PropagationConfig.cpp | 13 ++- source/Rule.cpp | 10 +-- source/Sanitizer.cpp | 6 ++ source/SourceSinkRule.cpp | 3 + source/TaintConfig.cpp | 25 +++++- source/constraints/FieldConstraints.cpp | 16 ++++ source/constraints/IntegerConstraint.cpp | 1 + source/constraints/MethodConstraints.cpp | 42 ++++++++++ source/constraints/ParameterConstraints.cpp | 6 ++ source/constraints/TypeConstraints.cpp | 14 ++++ source/model-generator/JsonModelGenerator.cpp | 5 ++ .../ModelGeneratorConfiguration.cpp | 1 + source/model-generator/ModelTemplates.cpp | 54 ++++++++++++- .../tests/MethodConstraintTest.cpp | 80 +++++++++---------- .../tests/ParameterConstraintTest.cpp | 24 +++--- 26 files changed, 328 insertions(+), 74 deletions(-) diff --git a/source/FeatureMayAlwaysSet.cpp b/source/FeatureMayAlwaysSet.cpp index a1adbca3..88ef9767 100644 --- a/source/FeatureMayAlwaysSet.cpp +++ b/source/FeatureMayAlwaysSet.cpp @@ -75,7 +75,8 @@ void FeatureMayAlwaysSet::add(const FeatureMayAlwaysSet& other) { FeatureMayAlwaysSet FeatureMayAlwaysSet::from_json( const Json::Value& value, - Context& context) { + Context& context, + bool check_unexpected_members) { auto may_features = FeatureSet{}; auto always_features = FeatureSet{}; @@ -84,6 +85,11 @@ FeatureMayAlwaysSet FeatureMayAlwaysSet::from_json( bool is_bottom = true; JsonValidation::validate_object(value); + if (check_unexpected_members) { + JsonValidation::check_unexpected_members( + value, {"may_features", "always_features"}); + } + if (value.isMember("may_features")) { JsonValidation::null_or_array(value, /* field */ "may_features"); may_features.join_with( diff --git a/source/FeatureMayAlwaysSet.h b/source/FeatureMayAlwaysSet.h index abc17486..3f2333f1 100644 --- a/source/FeatureMayAlwaysSet.h +++ b/source/FeatureMayAlwaysSet.h @@ -84,7 +84,8 @@ class FeatureMayAlwaysSet final static FeatureMayAlwaysSet from_json( const Json::Value& value, - Context& context); + Context& context, + bool check_unexpected_members = true); Json::Value to_json() const; friend std::ostream& operator<<( diff --git a/source/FieldModel.cpp b/source/FieldModel.cpp index bc1b9437..9fb6a26e 100644 --- a/source/FieldModel.cpp +++ b/source/FieldModel.cpp @@ -124,6 +124,9 @@ FieldModel FieldModel::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, {"field", "sources", "sinks"}); + FieldModel model(field); for (auto source_value : diff --git a/source/JsonValidation.cpp b/source/JsonValidation.cpp index 95b8b3aa..7a59f2a0 100644 --- a/source/JsonValidation.cpp +++ b/source/JsonValidation.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -334,4 +335,29 @@ void JsonValidation::update_object( } } +void JsonValidation::check_unexpected_members( + const Json::Value& value, + const std::unordered_set& valid_members) { + validate_object(value); + + for (const std::string& member : value.getMemberNames()) { + if (valid_members.find(member) == valid_members.end()) { + throw JsonValidationError( + value, + /* field */ std::nullopt, + /* expected */ + fmt::format( + "fields {}, got `{}`", + fmt::join( + boost::adaptors::transform( + valid_members, + [](const std::string& member) { + return fmt::format("`{}`", member); + }), + ", "), + member)); + } + } +} + } // namespace marianatrench diff --git a/source/JsonValidation.h b/source/JsonValidation.h index 23232ee0..75c323a9 100644 --- a/source/JsonValidation.h +++ b/source/JsonValidation.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -87,6 +88,11 @@ class JsonValidation final { * object, in place. */ static void update_object(Json::Value& left, const Json::Value& right); + + /* Error on invalid members of a json object. */ + static void check_unexpected_members( + const Json::Value& value, + const std::unordered_set& valid_members); }; } // namespace marianatrench diff --git a/source/Kind.cpp b/source/Kind.cpp index 5757e85b..61f72262 100644 --- a/source/Kind.cpp +++ b/source/Kind.cpp @@ -22,7 +22,13 @@ std::ostream& operator<<(std::ostream& out, const Kind& kind) { return out; } -const Kind* Kind::from_json(const Json::Value& value, Context& context) { +const Kind* Kind::from_json( + const Json::Value& value, + Context& context, + bool check_unexpected_members) { + if (check_unexpected_members) { + JsonValidation::check_unexpected_members(value, {"kind", "partial_label"}); + } const auto leaf_kind = JsonValidation::string(value, /* field */ "kind"); if (value.isMember("partial_label")) { return context.kind_factory->get_partial( diff --git a/source/Kind.h b/source/Kind.h index 48b400a2..7d583ae4 100644 --- a/source/Kind.h +++ b/source/Kind.h @@ -60,7 +60,10 @@ class Kind { * If it is known beforehand (as in Rules) whether the Kind is a Named or * Partial kind then use the override of this method from the specific Kind. */ - static const Kind* from_json(const Json::Value& value, Context& context); + static const Kind* from_json( + const Json::Value& value, + Context& context, + bool check_unexpected_members = true); /** * String value used for connecting traces of the same kind. diff --git a/source/Method.cpp b/source/Method.cpp index b38f76ae..c858028c 100644 --- a/source/Method.cpp +++ b/source/Method.cpp @@ -157,6 +157,9 @@ const Method* Method::from_json(const Json::Value& value, Context& context) { value, /* field */ std::nullopt, /* expected */ "object or string"); } + JsonValidation::check_unexpected_members( + value, {"name", "parameter_type_overrides"}); + auto method_name = JsonValidation::string(value, "name"); auto* dex_method = redex::get_method(method_name); @@ -168,6 +171,8 @@ const Method* Method::from_json(const Json::Value& value, Context& context) { ParameterTypeOverrides parameter_type_overrides; for (auto parameter_type_override : JsonValidation::null_or_array(value, "parameter_type_overrides")) { + JsonValidation::check_unexpected_members( + parameter_type_override, {"parameter", "type"}); auto parameter = JsonValidation::integer(parameter_type_override, "parameter"); auto* type = JsonValidation::dex_type(parameter_type_override, "type"); diff --git a/source/Model.cpp b/source/Model.cpp index b5383583..1bb68b5a 100644 --- a/source/Model.cpp +++ b/source/Model.cpp @@ -959,8 +959,30 @@ void Model::join_with(const Model& other) { Model Model::from_json( const Method* method, const Json::Value& value, - Context& context) { + Context& context, + bool check_unexpected_members) { JsonValidation::validate_object(value); + if (check_unexpected_members) { + JsonValidation::check_unexpected_members( + value, + {"method", // Only when called from `Registry`. + "modes", + "freeze", + "generations", + "parameter_sources", + "sources", + "sinks", + "effect_sources", + "effect_sinks", + "propagation", + "sanitizers", + "attach_to_sources", + "attach_to_sinks", + "attach_to_propagations", + "add_features_to_arguments", + "inline_as", + "issues"}); + } Modes modes; for (auto mode_value : @@ -1075,6 +1097,8 @@ Model Model::from_json( for (auto attach_to_sources_value : JsonValidation::null_or_array(value, /* field */ "attach_to_sources")) { + JsonValidation::check_unexpected_members( + attach_to_sources_value, {"port", "features"}); JsonValidation::string(attach_to_sources_value, /* field */ "port"); auto root = Root::from_json(attach_to_sources_value["port"]); JsonValidation::null_or_array( @@ -1086,6 +1110,8 @@ Model Model::from_json( for (auto attach_to_sinks_value : JsonValidation::null_or_array(value, /* field */ "attach_to_sinks")) { + JsonValidation::check_unexpected_members( + attach_to_sinks_value, {"port", "features"}); JsonValidation::string(attach_to_sinks_value, /* field */ "port"); auto root = Root::from_json(attach_to_sinks_value["port"]); JsonValidation::null_or_array( @@ -1097,6 +1123,8 @@ Model Model::from_json( for (auto attach_to_propagations_value : JsonValidation::null_or_array( value, /* field */ "attach_to_propagations")) { + JsonValidation::check_unexpected_members( + attach_to_propagations_value, {"port", "features"}); JsonValidation::string(attach_to_propagations_value, /* field */ "port"); auto root = Root::from_json(attach_to_propagations_value["port"]); JsonValidation::null_or_array( @@ -1108,6 +1136,8 @@ Model Model::from_json( for (auto add_features_to_arguments_value : JsonValidation::null_or_array( value, /* field */ "add_features_to_arguments")) { + JsonValidation::check_unexpected_members( + add_features_to_arguments_value, {"port", "features"}); JsonValidation::string(add_features_to_arguments_value, /* field */ "port"); auto root = Root::from_json(add_features_to_arguments_value["port"]); JsonValidation::null_or_array( diff --git a/source/Model.h b/source/Model.h index 7e33e33d..c7a223b8 100644 --- a/source/Model.h +++ b/source/Model.h @@ -308,7 +308,8 @@ class Model final { static Model from_json( const Method* MT_NULLABLE method, const Json::Value& value, - Context& context); + Context& context, + bool check_unexpected_members = true); Json::Value to_json(ExportOriginsMode export_origins_mode) const; /* Export the model to json and include the method position. */ diff --git a/source/MultiSourceMultiSinkRule.cpp b/source/MultiSourceMultiSinkRule.cpp index 3a42bdeb..71649033 100644 --- a/source/MultiSourceMultiSinkRule.cpp +++ b/source/MultiSourceMultiSinkRule.cpp @@ -41,6 +41,9 @@ std::unique_ptr MultiSourceMultiSinkRule::from_json( const std::string& description, const Json::Value& value, Context& context) { + JsonValidation::check_unexpected_members( + value, {"name", "code", "description", "multi_sources", "partial_sinks"}); + const auto& sources = JsonValidation::object(value, "multi_sources"); const auto& labels = sources.getMemberNames(); diff --git a/source/PropagationConfig.cpp b/source/PropagationConfig.cpp index 82088447..8dd312cb 100644 --- a/source/PropagationConfig.cpp +++ b/source/PropagationConfig.cpp @@ -15,6 +15,16 @@ PropagationConfig PropagationConfig::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, + {"output", + "input", + "may_features", + "always_features", + "features", + "collapse", + "collapse-depth", + "transforms"}); JsonValidation::string(value, /* field */ "output"); auto output = AccessPath::from_json(value["output"]); @@ -42,7 +52,8 @@ PropagationConfig PropagationConfig::from_json( "an access path to an argument or supported call effect"); } - auto inferred_features = FeatureMayAlwaysSet::from_json(value, context); + auto inferred_features = FeatureMayAlwaysSet::from_json( + value, context, /* check_unexpected_members */ false); FeatureSet user_features = FeatureSet::from_json(value["features"], context); auto collapse_depth = CollapseDepth::zero(); diff --git a/source/Rule.cpp b/source/Rule.cpp index 6cc9955d..2390b111 100644 --- a/source/Rule.cpp +++ b/source/Rule.cpp @@ -32,12 +32,12 @@ std::unique_ptr Rule::from_json( value.isMember("multi_sources") && value.isMember("partial_sinks")) { return MultiSourceMultiSinkRule::from_json( name, code, description, value, context); + } else { + throw JsonValidationError( + value, + std::nullopt, + "keys: sources+sinks or multi_sources+partial_sinks"); } - - throw JsonValidationError( - value, - std::nullopt, - "keys: sources+sinks or multi_sources+partial_sinks"); } Json::Value Rule::to_json() const { diff --git a/source/Sanitizer.cpp b/source/Sanitizer.cpp index b477a59f..7104ffb3 100644 --- a/source/Sanitizer.cpp +++ b/source/Sanitizer.cpp @@ -94,6 +94,12 @@ const Sanitizer Sanitizer::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, + {"port", // Only when called from `TaintConfig::from_json`. + "sanitize", + "kinds"}); + SanitizerKind sanitizer_kind; auto sanitizer_kind_string = JsonValidation::string(value, /* field */ "sanitize"); diff --git a/source/SourceSinkRule.cpp b/source/SourceSinkRule.cpp index abf44116..452a15af 100644 --- a/source/SourceSinkRule.cpp +++ b/source/SourceSinkRule.cpp @@ -24,6 +24,9 @@ std::unique_ptr SourceSinkRule::from_json( const std::string& description, const Json::Value& value, Context& context) { + JsonValidation::check_unexpected_members( + value, {"name", "code", "description", "sources", "sinks", "transforms"}); + KindSet source_kinds; for (const auto& source_kind : JsonValidation::nonempty_array(value, /* field */ "sources")) { diff --git a/source/TaintConfig.cpp b/source/TaintConfig.cpp index 91023183..74e12753 100644 --- a/source/TaintConfig.cpp +++ b/source/TaintConfig.cpp @@ -91,8 +91,28 @@ AccessPath validate_and_infer_crtex_callee_port( TaintConfig TaintConfig::from_json(const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, + {"port", // Only when called from `Model::from_json` + "caller_port", // Only when called from `Model::from_json` + "type", // Only when called from `Model::from_json` for effects + "kind", + "partial_label", + "callee_port", + "callee", + "call_position", + "distance", + "origins", + "field_origins", + "features", + "may_features", + "always_features", + "via_type_of", + "via_value_of", + "canonical_names"}); - const Kind* kind = Kind::from_json(value, context); + const Kind* kind = + Kind::from_json(value, context, /* check_unexpected_members */ false); auto callee_port = AccessPath(Root(Root::Kind::Leaf)); if (value.isMember("callee_port")) { @@ -127,7 +147,8 @@ TaintConfig TaintConfig::from_json(const Json::Value& value, Context& context) { // features should go under "user_features", but this gives a way to override // that behavior and specify "may/always" features. Note that local inferred // features cannot be user-specified. - auto inferred_features = FeatureMayAlwaysSet::from_json(value, context); + auto inferred_features = FeatureMayAlwaysSet::from_json( + value, context, /* check_unexpected_members */ false); // User specified always-features. FeatureSet user_features; diff --git a/source/constraints/FieldConstraints.cpp b/source/constraints/FieldConstraints.cpp index b9d5d9fc..1d058602 100644 --- a/source/constraints/FieldConstraints.cpp +++ b/source/constraints/FieldConstraints.cpp @@ -179,23 +179,35 @@ std::unique_ptr FieldConstraint::from_json( std::string constraint_name = JsonValidation::string(constraint, "constraint"); if (constraint_name == "name") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "signature") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "signature_pattern") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "is_static") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique(expected); } else if (constraint_name == "not") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique(FieldConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "any_of" || constraint_name == "all_of") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inners"}); std::vector> constraints; for (const auto& inner : JsonValidation::null_or_array(constraint, /* field */ "inners")) { @@ -207,6 +219,8 @@ std::unique_ptr FieldConstraint::from_json( return std::make_unique(std::move(constraints)); } } else if (constraint_name == "has_annotation") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "type", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "type"), constraint.isMember("pattern") @@ -214,6 +228,8 @@ std::unique_ptr FieldConstraint::from_json( constraint, "pattern")} : std::nullopt); } else if (constraint_name == "parent") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "type", "inner"}); return std::make_unique(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else { diff --git a/source/constraints/IntegerConstraint.cpp b/source/constraints/IntegerConstraint.cpp index 2a69cb99..628693a5 100644 --- a/source/constraints/IntegerConstraint.cpp +++ b/source/constraints/IntegerConstraint.cpp @@ -45,6 +45,7 @@ bool IntegerConstraint::operator==(const IntegerConstraint& other) const { IntegerConstraint IntegerConstraint::from_json(const Json::Value& constraint) { JsonValidation::validate_object(constraint); + JsonValidation::check_unexpected_members(constraint, {"constraint", "value"}); auto constraint_name = JsonValidation::string(constraint, "constraint"); auto rhs = JsonValidation::integer(constraint, "value"); diff --git a/source/constraints/MethodConstraints.cpp b/source/constraints/MethodConstraints.cpp index d0f8f1da..8c847f65 100644 --- a/source/constraints/MethodConstraints.cpp +++ b/source/constraints/MethodConstraints.cpp @@ -588,9 +588,13 @@ std::unique_ptr MethodConstraint::from_json( std::string constraint_name = JsonValidation::string(constraint, "constraint"); if (constraint_name == "name") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "pattern")); } else if (constraint_name == "parent") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner", "pattern"}); if (constraint.isMember("inner") && constraint.isMember("pattern")) { throw JsonValidationError( constraint, @@ -612,42 +616,67 @@ std::unique_ptr MethodConstraint::from_json( JsonValidation::string(constraint, /* field */ "pattern"))); } } else if (constraint_name == "number_parameters") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique( IntegerConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "number_overrides") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique( IntegerConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner")), context); } else if (constraint_name == "is_static") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique(expected); } else if (constraint_name == "is_constructor") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique(expected); } else if (constraint_name == "is_native") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique(expected); } else if (constraint_name == "parameter") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "idx", "inner"}); int index = JsonValidation::integer(constraint, /* field */ "idx"); return std::make_unique( index, ParameterConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "signature") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "signature_pattern") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "signature_match") { + JsonValidation::check_unexpected_members( + constraint, + {"constraint", + "name", + "names", + "parent", + "parents", + "extends", + "include_self"}); std::vector> constraints; int name_count = 0; int parent_count = 0; @@ -702,9 +731,13 @@ std::unique_ptr MethodConstraint::from_json( } return std::make_unique(std::move(constraints)); } else if (constraint_name == "bytecode") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "any_of" || constraint_name == "all_of") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inners"}); std::vector> constraints; for (const auto& inner : JsonValidation::null_or_array(constraint, /* field */ "inners")) { @@ -716,9 +749,12 @@ std::unique_ptr MethodConstraint::from_json( return std::make_unique(std::move(constraints)); } } else if (constraint_name == "return") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "visibility") { + JsonValidation::check_unexpected_members(constraint, {"constraint", "is"}); auto visibility_string = JsonValidation::string(constraint, /* field */ "is"); auto visibility = string_to_visibility(visibility_string); @@ -730,14 +766,20 @@ std::unique_ptr MethodConstraint::from_json( } return std::make_unique(*visibility); } else if (constraint_name == "not") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique(MethodConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"), context)); } else if (constraint_name == "has_code") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique(expected); } else if (constraint_name == "has_annotation") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "type", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "type"), constraint.isMember("pattern") diff --git a/source/constraints/ParameterConstraints.cpp b/source/constraints/ParameterConstraints.cpp index 3aae815e..e87c6e47 100644 --- a/source/constraints/ParameterConstraints.cpp +++ b/source/constraints/ParameterConstraints.cpp @@ -149,6 +149,8 @@ std::unique_ptr ParameterConstraint::from_json( std::string constraint_name = JsonValidation::string(constraint, "constraint"); if (constraint_name == "any_of" || constraint_name == "all_of") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inners"}); std::vector> constraints; for (const auto& inner : JsonValidation::null_or_array(constraint, /* field */ "inners")) { @@ -160,10 +162,14 @@ std::unique_ptr ParameterConstraint::from_json( return std::make_unique(std::move(constraints)); } } else if (constraint_name == "not") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique( ParameterConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "parameter_has_annotation") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "type", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "type"), constraint.isMember("pattern") diff --git a/source/constraints/TypeConstraints.cpp b/source/constraints/TypeConstraints.cpp index 36cd26fa..48550154 100644 --- a/source/constraints/TypeConstraints.cpp +++ b/source/constraints/TypeConstraints.cpp @@ -342,9 +342,13 @@ std::unique_ptr TypeConstraint::from_json( std::string constraint_name = JsonValidation::string(constraint, "constraint"); if (constraint_name == "name") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "pattern")); } else if (constraint_name == "extends") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner", "include_self"}); bool includes_self = constraint.isMember("include_self") ? JsonValidation::boolean(constraint, /* field */ "include_self") : true; @@ -353,12 +357,18 @@ std::unique_ptr TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner")), includes_self); } else if (constraint_name == "super") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "not") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inner"}); return std::make_unique(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "any_of" || constraint_name == "all_of") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "inners"}); std::vector> constraints; for (const auto& inner : JsonValidation::null_or_array(constraint, /* field */ "inners")) { @@ -370,6 +380,8 @@ std::unique_ptr TypeConstraint::from_json( return std::make_unique(std::move(constraints)); } } else if (constraint_name == "has_annotation") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "type", "pattern"}); return std::make_unique( JsonValidation::string(constraint, "type"), constraint.isMember("pattern") @@ -379,6 +391,8 @@ std::unique_ptr TypeConstraint::from_json( } else if ( constraint_name == "is_class" || constraint_name == "is_interface" || constraint_name == "is_enum") { + JsonValidation::check_unexpected_members( + constraint, {"constraint", "value"}); bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; diff --git a/source/model-generator/JsonModelGenerator.cpp b/source/model-generator/JsonModelGenerator.cpp index 97b856a7..40ffbd29 100644 --- a/source/model-generator/JsonModelGenerator.cpp +++ b/source/model-generator/JsonModelGenerator.cpp @@ -108,8 +108,13 @@ JsonModelGenerator::JsonModelGenerator( const Json::Value& value = JsonValidation::parse_json_file(json_configuration_file); + JsonValidation::check_unexpected_members(value, {"model_generators"}); + for (auto model_generator : JsonValidation::nonempty_array(value, /* field */ "model_generators")) { + JsonValidation::check_unexpected_members( + model_generator, {"find", "where", "model", "verbosity"}); + int verbosity = model_generator.isMember("verbosity") ? JsonValidation::integer(model_generator, "verbosity") : 5; // default verbosity diff --git a/source/model-generator/ModelGeneratorConfiguration.cpp b/source/model-generator/ModelGeneratorConfiguration.cpp index 9f1af913..9091e28c 100644 --- a/source/model-generator/ModelGeneratorConfiguration.cpp +++ b/source/model-generator/ModelGeneratorConfiguration.cpp @@ -18,6 +18,7 @@ ModelGeneratorConfiguration::ModelGeneratorConfiguration( ModelGeneratorConfiguration ModelGeneratorConfiguration::from_json( const Json::Value& value) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members(value, {"name"}); return ModelGeneratorConfiguration(JsonValidation::string(value, "name")); } diff --git a/source/model-generator/ModelTemplates.cpp b/source/model-generator/ModelTemplates.cpp index ba27377c..7538d004 100644 --- a/source/model-generator/ModelTemplates.cpp +++ b/source/model-generator/ModelTemplates.cpp @@ -178,6 +178,16 @@ PropagationTemplate PropagationTemplate::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, + {"input", + "output", + "may_features", + "always_features", + "features", + "transforms", + "collapse", + "collapse-depth"}); JsonValidation::string(value, /* field */ "input"); auto input = AccessPathTemplate::from_json(value["input"]); @@ -190,7 +200,8 @@ PropagationTemplate PropagationTemplate::from_json( JsonValidation::string(value, /* field */ "output"); auto output = AccessPathTemplate::from_json(value["output"]); - auto inferred_features = FeatureMayAlwaysSet::from_json(value, context); + auto inferred_features = FeatureMayAlwaysSet::from_json( + value, context, /* check_unexpected_members */ false); auto user_features = FeatureSet::from_json(value["features"], context); const TransformList* transforms = nullptr; @@ -357,6 +368,7 @@ AttachToSourcesTemplate AttachToSourcesTemplate::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members(value, {"port", "features"}); JsonValidation::null_or_array(value, /* field */ "features"); auto features = FeatureSet::from_json(value["features"], context); @@ -390,6 +402,7 @@ AttachToSinksTemplate AttachToSinksTemplate::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members(value, {"port", "features"}); JsonValidation::null_or_array(value, /* field */ "features"); auto features = FeatureSet::from_json(value["features"], context); @@ -422,6 +435,7 @@ AttachToPropagationsTemplate AttachToPropagationsTemplate::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members(value, {"port", "features"}); JsonValidation::null_or_array(value, /* field */ "features"); auto features = FeatureSet::from_json(value["features"], context); @@ -455,6 +469,7 @@ AddFeaturesToArgumentsTemplate AddFeaturesToArgumentsTemplate::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members(value, {"port", "features"}); JsonValidation::null_or_array(value, /* field */ "features"); auto features = FeatureSet::from_json(value["features"], context); @@ -510,6 +525,19 @@ ForAllParameters ForAllParameters::from_json( const Json::Value& value, Context& context) { JsonValidation::validate_object(value); + JsonValidation::check_unexpected_members( + value, + {"variable", + "where", + "sinks", + "parameter_sources", + "generations", + "sources", + "propagation", + "attach_to_sources", + "attach_to_sinks", + "attach_to_propagations", + "add_features_to_arguments"}); std::vector> constraints; std::vector sink_templates; @@ -702,6 +730,25 @@ ModelTemplate ModelTemplate::from_json( const Json::Value& model, Context& context) { JsonValidation::validate_object(model); + JsonValidation::check_unexpected_members( + model, + {"for_all_parameters", + "modes", + "freeze", + "generations", + "parameter_sources", + "sources", + "sinks", + "effect_sources", + "effect_sinks", + "propagation", + "sanitizers", + "attach_to_sources", + "attach_to_sinks", + "attach_to_propagations", + "add_features_to_arguments", + "inline_as", + "issues"}); std::vector for_all_parameters; for (auto const& value : @@ -712,7 +759,10 @@ ModelTemplate ModelTemplate::from_json( return ModelTemplate( Model::from_json( - /* method */ nullptr, model, context), + /* method */ nullptr, + model, + context, + /* check_unexpected_members */ false), std::move(for_all_parameters)); } diff --git a/source/model-generator/tests/MethodConstraintTest.cpp b/source/model-generator/tests/MethodConstraintTest.cpp index 1ae85417..72ff9cc0 100644 --- a/source/model-generator/tests/MethodConstraintTest.cpp +++ b/source/model-generator/tests/MethodConstraintTest.cpp @@ -1041,16 +1041,15 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { EXPECT_EQ(IsStaticConstraint(false), *constraint); } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "is_static", "vAlue": false })"), - context); - EXPECT_EQ(IsStaticConstraint(true), *constraint); - } + context), + JsonValidationError); { auto constraint = MethodConstraint::from_json( @@ -1095,16 +1094,15 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { EXPECT_EQ(IsConstructorConstraint(false), *constraint); } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "is_constructor", "vAlue": false })"), - context); - EXPECT_EQ(IsConstructorConstraint(true), *constraint); - } + context), + JsonValidationError); { auto constraint = MethodConstraint::from_json( @@ -1149,16 +1147,15 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { EXPECT_EQ(IsNativeConstraint(false), *constraint); } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "is_native", "vAlue": false })"), - context); - EXPECT_EQ(IsNativeConstraint(true), *constraint); - } + context), + JsonValidationError); { auto constraint = MethodConstraint::from_json( @@ -1203,16 +1200,15 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { EXPECT_EQ(HasCodeConstraint(false), *constraint); } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "has_code", "vAlue": false })"), - context); - EXPECT_EQ(HasCodeConstraint(true), *constraint); - } + context), + JsonValidationError); { auto constraint = MethodConstraint::from_json( @@ -1678,10 +1674,10 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { } } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "any_of", "inNers": [ { @@ -1694,10 +1690,8 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { } ] })"), - context); - std::vector> constraints; - EXPECT_EQ(AnyOfMethodConstraint(std::move(constraints)), *constraint); - } + context), + JsonValidationError); EXPECT_THROW( MethodConstraint::from_json( @@ -1781,10 +1775,10 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { } } - { - auto constraint = MethodConstraint::from_json( - test::parse_json( - R"({ + EXPECT_THROW( + MethodConstraint::from_json( + test::parse_json( + R"({ "constraint": "all_of", "inNers": [ { @@ -1797,10 +1791,8 @@ TEST_F(MethodConstraintTest, MethodConstraintFromJson) { } ] })"), - context); - std::vector> constraints; - EXPECT_EQ(AllOfMethodConstraint(std::move(constraints)), *constraint); - } + context), + JsonValidationError); EXPECT_THROW( MethodConstraint::from_json( diff --git a/source/model-generator/tests/ParameterConstraintTest.cpp b/source/model-generator/tests/ParameterConstraintTest.cpp index 3f393862..1b0f47c5 100644 --- a/source/model-generator/tests/ParameterConstraintTest.cpp +++ b/source/model-generator/tests/ParameterConstraintTest.cpp @@ -198,9 +198,9 @@ TEST_F(ParameterConstraintTest, ParameterConstraintFromJson) { } } - { - auto constraint = ParameterConstraint::from_json(test::parse_json( - R"({ + EXPECT_THROW( + ParameterConstraint::from_json(test::parse_json( + R"({ "constraint": "any_of", "inNers": [ { @@ -213,10 +213,8 @@ TEST_F(ParameterConstraintTest, ParameterConstraintFromJson) { "pattern": "A" } ] - })")); - std::vector> constraints; - EXPECT_EQ(AnyOfParameterConstraint(std::move(constraints)), *constraint); - } + })")), + JsonValidationError); EXPECT_THROW( ParameterConstraint::from_json(test::parse_json( @@ -292,9 +290,9 @@ TEST_F(ParameterConstraintTest, ParameterConstraintFromJson) { } } - { - auto constraint = ParameterConstraint::from_json(test::parse_json( - R"({ + EXPECT_THROW( + ParameterConstraint::from_json(test::parse_json( + R"({ "constraint": "all_of", "inNers": [ { @@ -307,10 +305,8 @@ TEST_F(ParameterConstraintTest, ParameterConstraintFromJson) { "pattern": "A" } ] - })")); - std::vector> constraints; - EXPECT_EQ(AllOfParameterConstraint(std::move(constraints)), *constraint); - } + })")), + JsonValidationError); EXPECT_THROW( ParameterConstraint::from_json(test::parse_json(