source/model-generator/MethodConstraints.cpp (478 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <mariana-trench/JsonValidation.h> #include <mariana-trench/Log.h> #include <mariana-trench/Overrides.h> #include <mariana-trench/RE2.h> #include <mariana-trench/model-generator/MethodConstraints.h> #include <mariana-trench/model-generator/TypeConstraints.h> namespace marianatrench { bool has_annotation( const DexAnnotationSet* annotations_set, const std::string& expected_type, const std::optional<re2::RE2>& expected_annotation) { if (!annotations_set) { return false; } for (const auto& annotation : annotations_set->get_annotations()) { DexType* annotation_type = annotation->type(); if (!annotation_type || annotation_type->str() != expected_type) { continue; } // If annotation is not specified, then we have found the annotation // satisfying the specified type if (!expected_annotation) { return true; } for (const auto& element : annotation->anno_elems()) { if (re2::RE2::FullMatch( element.encoded_value->show(), *expected_annotation)) { LOG(4, "Found annotation type {} value {}.", annotation_type->str(), element.encoded_value->show()); return true; } } } return false; } MethodNameConstraint::MethodNameConstraint(const std::string& regex_string) : pattern_(regex_string) {} MethodHashedSet MethodNameConstraint::may_satisfy( const MethodMappings& method_mappings) const { auto string_pattern = as_string_literal(pattern_); if (!string_pattern) { return MethodHashedSet::top(); } return method_mappings.name_to_methods.get( *string_pattern, MethodHashedSet::bottom()); } bool MethodNameConstraint::satisfy(const Method* method) const { return re2::RE2::FullMatch(method->get_name(), pattern_); } bool MethodNameConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const MethodNameConstraint*>(&other)) { return other_constraint->pattern_.pattern() == pattern_.pattern(); } else { return false; } } ParentConstraint::ParentConstraint( std::unique_ptr<TypeConstraint> inner_constraint) : inner_constraint_(std::move(inner_constraint)) {} MethodHashedSet ParentConstraint::may_satisfy( const MethodMappings& method_mappings) const { return inner_constraint_->may_satisfy( method_mappings, MaySatisfyMethodConstraintKind::Parent); } bool ParentConstraint::satisfy(const Method* method) const { return inner_constraint_->satisfy(method->get_class()); } bool ParentConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const ParentConstraint*>(&other)) { return *(other_constraint->inner_constraint_) == *inner_constraint_; } else { return false; } } AllOfMethodConstraint::AllOfMethodConstraint( std::vector<std::unique_ptr<MethodConstraint>> constraints) : constraints_(std::move(constraints)) {} bool AllOfMethodConstraint::has_children() const { return true; } std::vector<const MethodConstraint*> AllOfMethodConstraint::children() const { std::vector<const MethodConstraint*> constraints; for (const auto& constraint : constraints_) { constraints.push_back(constraint.get()); } return constraints; } MethodHashedSet AllOfMethodConstraint::may_satisfy( const MethodMappings& method_mappings) const { auto intersection_set = MethodHashedSet::top(); for (const auto& constraint : constraints_) { intersection_set.meet_with(constraint->may_satisfy(method_mappings)); } return intersection_set; } bool AllOfMethodConstraint::satisfy(const Method* method) const { return std::all_of( constraints_.begin(), constraints_.end(), [method](const auto& constraint) { return constraint->satisfy(method); }); } bool AllOfMethodConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const AllOfMethodConstraint*>(&other)) { return std::is_permutation( other_constraint->constraints_.begin(), other_constraint->constraints_.end(), constraints_.begin(), constraints_.end(), [](const auto& left, const auto& right) { return *left == *right; }); } else { return false; } } AnyOfMethodConstraint::AnyOfMethodConstraint( std::vector<std::unique_ptr<MethodConstraint>> constraints) : constraints_(std::move(constraints)) {} bool AnyOfMethodConstraint::has_children() const { return true; } std::vector<const MethodConstraint*> AnyOfMethodConstraint::children() const { std::vector<const MethodConstraint*> constraints; for (const auto& constraint : constraints_) { constraints.push_back(constraint.get()); } return constraints; } MethodHashedSet AnyOfMethodConstraint::may_satisfy( const MethodMappings& method_mappings) const { if (constraints_.empty()) { return MethodHashedSet::top(); } auto union_set = MethodHashedSet::bottom(); for (const auto& constraint : constraints_) { union_set.join_with(constraint->may_satisfy(method_mappings)); } return union_set; } bool AnyOfMethodConstraint::satisfy(const Method* method) const { // If there is no constraint, the method vacuously satisfies the constraint // This is different from the semantic of std::any_of if (constraints_.empty()) { return true; } else { return std::any_of( constraints_.begin(), constraints_.end(), [method](const auto& constraint) { return constraint->satisfy(method); }); } } bool AnyOfMethodConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const AnyOfMethodConstraint*>(&other)) { return std::is_permutation( other_constraint->constraints_.begin(), other_constraint->constraints_.end(), constraints_.begin(), constraints_.end(), [](const auto& left, const auto& right) { return *left == *right; }); } else { return false; } } NotMethodConstraint::NotMethodConstraint( std::unique_ptr<MethodConstraint> constraint) : constraint_(std::move(constraint)) {} bool NotMethodConstraint::has_children() const { return true; } std::vector<const MethodConstraint*> NotMethodConstraint::children() const { return {constraint_.get()}; } MethodHashedSet NotMethodConstraint::may_satisfy( const MethodMappings& method_mappings) const { MethodHashedSet child_methods = constraint_->may_satisfy(method_mappings); if (child_methods.is_top() || child_methods.is_bottom()) { return MethodHashedSet::top(); } MethodHashedSet all_methods = method_mappings.all_methods; all_methods.difference_with(child_methods); return all_methods; } bool NotMethodConstraint::satisfy(const Method* method) const { return !constraint_->satisfy(method); } bool NotMethodConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const NotMethodConstraint*>(&other)) { return *(other_constraint->constraint_) == *constraint_; } else { return false; } } NumberParametersConstraint::NumberParametersConstraint( IntegerConstraint constraint) : constraint_(constraint){}; bool NumberParametersConstraint::satisfy(const Method* method) const { return constraint_.satisfy(method->number_of_parameters()); } bool NumberParametersConstraint::operator==( const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const NumberParametersConstraint*>(&other)) { return other_constraint->constraint_ == constraint_; } else { return false; } } NumberOverridesConstraint::NumberOverridesConstraint( IntegerConstraint constraint, Context& context) : constraint_(constraint), context_(context){}; bool NumberOverridesConstraint::satisfy(const Method* method) const { return constraint_.satisfy(context_.overrides->get(method).size()); } bool NumberOverridesConstraint::operator==( const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const NumberOverridesConstraint*>(&other)) { return other_constraint->constraint_ == constraint_; } else { return false; } } IsStaticConstraint::IsStaticConstraint(bool expected) : expected_(expected) {} bool IsStaticConstraint::satisfy(const Method* method) const { return method->is_static() == expected_; } bool IsStaticConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const IsStaticConstraint*>(&other)) { return other_constraint->expected_ == expected_; } else { return false; } } IsConstructorConstraint::IsConstructorConstraint(bool expected) : expected_(expected) {} bool IsConstructorConstraint::satisfy(const Method* method) const { return method->is_constructor() == expected_; } bool IsConstructorConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const IsConstructorConstraint*>(&other)) { return other_constraint->expected_ == expected_; } else { return false; } } IsNativeConstraint::IsNativeConstraint(bool expected) : expected_(expected) {} bool IsNativeConstraint::satisfy(const Method* method) const { return method->is_native() == expected_; } bool IsNativeConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const IsNativeConstraint*>(&other)) { return other_constraint->expected_ == expected_; } else { return false; } } HasCodeConstraint::HasCodeConstraint(bool expected) : expected_(expected) {} bool HasCodeConstraint::satisfy(const Method* method) const { return (method->get_code() != nullptr) == expected_; } bool HasCodeConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const HasCodeConstraint*>(&other)) { return other_constraint->expected_ == expected_; } else { return false; } } HasAnnotationMethodConstraint::HasAnnotationMethodConstraint( const std::string& type, const std::optional<std::string>& annotation) : type_(std::move(type)), annotation_(std::move(annotation)) {} bool HasAnnotationMethodConstraint::satisfy(const Method* method) const { return has_annotation( method->dex_method()->get_anno_set(), type_, annotation_); } bool HasAnnotationMethodConstraint::operator==( const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const HasAnnotationMethodConstraint*>(&other)) { return other_constraint->type_ == type_ && ((!other_constraint->annotation_ && !annotation_) || (other_constraint->annotation_ && annotation_ && other_constraint->annotation_->pattern() == annotation_->pattern())); } else { return false; } } ParameterConstraint::ParameterConstraint( ParameterPosition index, std::unique_ptr<TypeConstraint> inner_constraint) : index_(index), inner_constraint_(std::move(inner_constraint)) {} bool ParameterConstraint::satisfy(const Method* method) const { const auto type = method->parameter_type(index_); return type ? inner_constraint_->satisfy(type) : false; } bool ParameterConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const ParameterConstraint*>(&other)) { return other_constraint->index_ == index_ && *(other_constraint->inner_constraint_) == *inner_constraint_; } else { return false; } } SignatureConstraint::SignatureConstraint(const std::string& regex_string) : pattern_(regex_string) {} MethodHashedSet SignatureConstraint::may_satisfy( const MethodMappings& method_mappings) const { auto string_pattern = as_string_literal(pattern_); if (!string_pattern) { return MethodHashedSet::top(); } return method_mappings.signature_to_methods.get( *string_pattern, MethodHashedSet::bottom()); } bool SignatureConstraint::satisfy(const Method* method) const { return re2::RE2::FullMatch(method->signature(), pattern_); } bool SignatureConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const SignatureConstraint*>(&other)) { return other_constraint->pattern_.pattern() == pattern_.pattern(); } else { return false; } } ReturnConstraint::ReturnConstraint( std::unique_ptr<TypeConstraint> inner_constraint) : inner_constraint_(std::move(inner_constraint)) {} bool ReturnConstraint::satisfy(const Method* method) const { return inner_constraint_->satisfy(method->get_proto()->get_rtype()); } bool ReturnConstraint::operator==(const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const ReturnConstraint*>(&other)) { return *(other_constraint->inner_constraint_) == *inner_constraint_; } else { return false; } } VisibilityMethodConstraint::VisibilityMethodConstraint( DexAccessFlags visibility) : visibility_(visibility) {} bool VisibilityMethodConstraint::satisfy(const Method* method) const { return method->get_access() & visibility_; } bool VisibilityMethodConstraint::operator==( const MethodConstraint& other) const { if (auto* other_constraint = dynamic_cast<const VisibilityMethodConstraint*>(&other)) { return other_constraint->visibility_ == visibility_; } else { return false; } } bool MethodConstraint::has_children() const { return false; } std::vector<const MethodConstraint*> MethodConstraint::children() const { return {}; } MethodHashedSet MethodConstraint::may_satisfy( const MethodMappings& /* method_mappings */) const { return MethodHashedSet::top(); } namespace { std::optional<DexAccessFlags> string_to_visibility( const std::string& visibility) { if (visibility == "public") { return ACC_PUBLIC; } else if (visibility == "private") { return ACC_PRIVATE; } else if (visibility == "protected") { return ACC_PROTECTED; } else { return std::nullopt; } } } // namespace std::unique_ptr<MethodConstraint> MethodConstraint::from_json( const Json::Value& constraint, Context& context) { JsonValidation::validate_object(constraint); std::string constraint_name = JsonValidation::string(constraint, "constraint"); if (constraint_name == "name") { return std::make_unique<MethodNameConstraint>( JsonValidation::string(constraint, "pattern")); } else if (constraint_name == "parent") { return std::make_unique<ParentConstraint>(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "number_parameters") { return std::make_unique<NumberParametersConstraint>( IntegerConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "number_overrides") { return std::make_unique<NumberOverridesConstraint>( IntegerConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner")), context); } else if (constraint_name == "is_static") { bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique<IsStaticConstraint>(expected); } else if (constraint_name == "is_constructor") { bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique<IsConstructorConstraint>(expected); } else if (constraint_name == "is_native") { bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique<IsNativeConstraint>(expected); } else if (constraint_name == "parameter") { int index = JsonValidation::integer(constraint, /* field */ "idx"); return std::make_unique<ParameterConstraint>( index, TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "signature") { return std::make_unique<SignatureConstraint>( JsonValidation::string(constraint, /* field */ "pattern")); } else if (constraint_name == "any_of" || constraint_name == "all_of") { std::vector<std::unique_ptr<MethodConstraint>> constraints; for (const auto& inner : JsonValidation::null_or_array(constraint, /* field */ "inners")) { constraints.push_back(MethodConstraint::from_json(inner, context)); } if (constraint_name == "any_of") { return std::make_unique<AnyOfMethodConstraint>(std::move(constraints)); } else { return std::make_unique<AllOfMethodConstraint>(std::move(constraints)); } } else if (constraint_name == "return") { return std::make_unique<ReturnConstraint>(TypeConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"))); } else if (constraint_name == "visibility") { auto visibility_string = JsonValidation::string(constraint, /* field */ "is"); auto visibility = string_to_visibility(visibility_string); if (!visibility) { throw JsonValidationError( constraint, /* field */ "is", /* expected */ "`public`, `private` or `protected`"); } return std::make_unique<VisibilityMethodConstraint>(*visibility); } else if (constraint_name == "not") { return std::make_unique<NotMethodConstraint>(MethodConstraint::from_json( JsonValidation::object(constraint, /* field */ "inner"), context)); } else if (constraint_name == "has_code") { bool expected = constraint.isMember("value") ? JsonValidation::boolean(constraint, /* field */ "value") : true; return std::make_unique<HasCodeConstraint>(expected); } else if (constraint_name == "has_annotation") { return std::make_unique<HasAnnotationMethodConstraint>( JsonValidation::string(constraint, "type"), constraint.isMember("pattern") ? std::optional<std::string>{JsonValidation::string( constraint, "pattern")} : std::nullopt); } else { throw JsonValidationError( constraint, /* field */ "constraint", /* expected */ "valid constraint type"); return nullptr; } } } // namespace marianatrench