source/model-generator/ModelTemplates.cpp (541 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/model-generator/MethodConstraints.h>
#include <mariana-trench/model-generator/ModelTemplates.h>
#include <mariana-trench/model-generator/TypeConstraints.h>
namespace marianatrench {
TemplateVariableMapping::TemplateVariableMapping() {}
void TemplateVariableMapping::insert(
const std::string& name,
ParameterPosition index) {
map_.insert_or_assign(name, index);
}
std::optional<ParameterPosition> TemplateVariableMapping::at(
const std::string& name) const {
try {
return map_.at(name);
} catch (const std::out_of_range& exception) {
return std::nullopt;
}
}
ParameterPositionTemplate::ParameterPositionTemplate(
ParameterPosition parameter_position)
: parameter_position_(parameter_position) {}
ParameterPositionTemplate::ParameterPositionTemplate(
std::string parameter_position)
: parameter_position_(std::move(parameter_position)) {}
std::string ParameterPositionTemplate::to_string() const {
if (std::holds_alternative<ParameterPosition>(parameter_position_)) {
return std::to_string(std::get<ParameterPosition>(parameter_position_));
} else if (std::holds_alternative<std::string>(parameter_position_)) {
return std::get<std::string>(parameter_position_);
} else {
mt_unreachable();
}
}
ParameterPosition ParameterPositionTemplate::instantiate(
const TemplateVariableMapping& parameter_positions) const {
if (std::holds_alternative<ParameterPosition>(parameter_position_)) {
return std::get<ParameterPosition>(parameter_position_);
} else if (std::holds_alternative<std::string>(parameter_position_)) {
auto result =
parameter_positions.at(std::get<std::string>(parameter_position_));
if (result) {
return *result;
} else {
throw JsonValidationError(
Json::Value(std::get<std::string>(parameter_position_)),
/* field */ "parameter_position",
/* expected */ "a variable name that is defined in \"variable\"");
}
} else {
mt_unreachable();
}
}
RootTemplate::RootTemplate(
Root::Kind kind,
std::optional<ParameterPositionTemplate> parameter_position)
: kind_(kind), parameter_position_(parameter_position) {}
bool RootTemplate::is_argument() const {
return kind_ == Root::Kind::Argument;
}
std::string RootTemplate::to_string() const {
return is_argument()
? fmt::format("Argument({})", parameter_position_->to_string())
: "Return";
}
Root RootTemplate::instantiate(
const TemplateVariableMapping& parameter_positions) const {
switch (kind_) {
case Root::Kind::Return: {
return Root(Root::Kind::Return);
}
case Root::Kind::Argument: {
mt_assert(parameter_position_);
return Root(
Root::Kind::Argument,
parameter_position_->instantiate(parameter_positions));
}
default: {
mt_unreachable();
}
}
}
AccessPathTemplate::AccessPathTemplate(RootTemplate root, Path path)
: root_(root), path_(std::move(path)) {}
AccessPathTemplate AccessPathTemplate::from_json(const Json::Value& value) {
auto elements = AccessPath::split_path(value);
if (elements.empty()) {
throw JsonValidationError(
value, /* field */ std::nullopt, "non-empty string for access path");
}
// Parse the root.
auto root = RootTemplate(Root::Kind::Return);
const auto& root_string = elements.front();
if (boost::starts_with(root_string, "Argument(") &&
boost::ends_with(root_string, ")") && root_string.size() >= 11) {
auto parameter_string = root_string.substr(9, root_string.size() - 10);
auto parameter = parse_parameter_position(parameter_string);
if (parameter) {
root = RootTemplate(
Root::Kind::Argument, ParameterPositionTemplate(*parameter));
} else {
root = RootTemplate(
Root::Kind::Argument, ParameterPositionTemplate(parameter_string));
}
} else if (root_string != "Return") {
throw JsonValidationError(
value,
/* field */ std::nullopt,
fmt::format(
"valid access path root (`Return` or `Argument(...)`), got `{}`",
root_string));
}
Path path;
for (auto iterator = std::next(elements.begin()), end = elements.end();
iterator != end;
++iterator) {
path.append(DexString::make_string(*iterator));
}
return AccessPathTemplate(root, path);
}
Json::Value AccessPathTemplate::to_json() const {
std::string value;
value.append(root_.to_string());
for (auto* field : path_) {
value.append(".");
value.append(show(field));
}
return value;
}
AccessPath AccessPathTemplate::instantiate(
const TemplateVariableMapping& parameter_positions) const {
return AccessPath(root_.instantiate(parameter_positions), path_);
}
PropagationTemplate::PropagationTemplate(
AccessPathTemplate input,
AccessPathTemplate output,
FeatureMayAlwaysSet inferred_features,
FeatureSet user_features)
: input_(std::move(input)),
output_(std::move(output)),
inferred_features_(std::move(inferred_features)),
user_features_(std::move(user_features)) {}
PropagationTemplate PropagationTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::string(value, /* field */ "input");
auto input = AccessPathTemplate::from_json(value["input"]);
if (!input.root().is_argument()) {
throw JsonValidationError(
value, /* field */ "input", "an access path to an argument");
}
JsonValidation::string(value, /* field */ "output");
auto output = AccessPathTemplate::from_json(value["output"]);
auto inferred_features = FeatureMayAlwaysSet::from_json(value, context);
auto user_features = FeatureSet::from_json(value["features"], context);
return PropagationTemplate(input, output, inferred_features, user_features);
}
void PropagationTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_propagation(
Propagation(
input_.instantiate(parameter_positions),
inferred_features_,
user_features_),
output_.instantiate(parameter_positions));
}
SinkTemplate::SinkTemplate(Frame sink, AccessPathTemplate port)
: sink_(std::move(sink)), port_(std::move(port)) {}
SinkTemplate SinkTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::string(value, /* field */ "port");
return SinkTemplate(
Frame::from_json(value, context),
AccessPathTemplate::from_json(value["port"]));
}
void SinkTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_sink(port_.instantiate(parameter_positions), sink_);
}
ParameterSourceTemplate::ParameterSourceTemplate(
Frame source,
AccessPathTemplate port)
: source_(std::move(source)), port_(std::move(port)) {}
ParameterSourceTemplate ParameterSourceTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::string(value, /* field */ "port");
return ParameterSourceTemplate(
Frame::from_json(value, context),
AccessPathTemplate::from_json(value["port"]));
}
void ParameterSourceTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_parameter_source(port_.instantiate(parameter_positions), source_);
}
GenerationTemplate::GenerationTemplate(Frame source, AccessPathTemplate port)
: source_(std::move(source)), port_(std::move(port)) {}
GenerationTemplate GenerationTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::string(value, /* field */ "port");
return GenerationTemplate(
Frame::from_json(value, context),
AccessPathTemplate::from_json(value["port"]));
}
void GenerationTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_generation(port_.instantiate(parameter_positions), source_);
}
SourceTemplate::SourceTemplate(Frame source, AccessPathTemplate port)
: source_(std::move(source)), port_(std::move(port)) {}
SourceTemplate SourceTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::string(value, /* field */ "port");
return SourceTemplate(
Frame::from_json(value, context),
AccessPathTemplate::from_json(value["port"]));
}
void SourceTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
if (port_.root().is_argument()) {
model.add_parameter_source(port_.instantiate(parameter_positions), source_);
} else {
model.add_generation(port_.instantiate(parameter_positions), source_);
}
}
AttachToSourcesTemplate::AttachToSourcesTemplate(
FeatureSet features,
RootTemplate port)
: features_(std::move(features)), port_(std::move(port)) {}
AttachToSourcesTemplate AttachToSourcesTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::null_or_array(value, /* field */ "features");
auto features = FeatureSet::from_json(value["features"], context);
JsonValidation::string(value, /* field */ "port");
auto port = AccessPathTemplate::from_json(value["port"]);
if (!port.path().empty()) {
throw JsonValidationError(
value,
/* field */ "port",
/* expected */ "an access path root without field");
}
return AttachToSourcesTemplate(features, port.root());
}
void AttachToSourcesTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_attach_to_sources(
port_.instantiate(parameter_positions), features_);
}
AttachToSinksTemplate::AttachToSinksTemplate(
FeatureSet features,
RootTemplate port)
: features_(std::move(features)), port_(std::move(port)) {}
AttachToSinksTemplate AttachToSinksTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::null_or_array(value, /* field */ "features");
auto features = FeatureSet::from_json(value["features"], context);
JsonValidation::string(value, /* field */ "port");
auto port = AccessPathTemplate::from_json(value["port"]);
if (!port.path().empty()) {
throw JsonValidationError(
value,
/* field */ "port",
/* expected */ "an access path root without field");
}
return AttachToSinksTemplate(features, port.root());
}
void AttachToSinksTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_attach_to_sinks(port_.instantiate(parameter_positions), features_);
}
AttachToPropagationsTemplate::AttachToPropagationsTemplate(
FeatureSet features,
RootTemplate port)
: features_(std::move(features)), port_(std::move(port)) {}
AttachToPropagationsTemplate AttachToPropagationsTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::null_or_array(value, /* field */ "features");
auto features = FeatureSet::from_json(value["features"], context);
JsonValidation::string(value, /* field */ "port");
auto port = AccessPathTemplate::from_json(value["port"]);
if (!port.path().empty()) {
throw JsonValidationError(
value,
/* field */ "port",
/* expected */ "an access path root without field");
}
return AttachToPropagationsTemplate(features, port.root());
}
void AttachToPropagationsTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_attach_to_propagations(
port_.instantiate(parameter_positions), features_);
}
AddFeaturesToArgumentsTemplate::AddFeaturesToArgumentsTemplate(
FeatureSet features,
RootTemplate port)
: features_(std::move(features)), port_(std::move(port)) {}
AddFeaturesToArgumentsTemplate AddFeaturesToArgumentsTemplate::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
JsonValidation::null_or_array(value, /* field */ "features");
auto features = FeatureSet::from_json(value["features"], context);
JsonValidation::string(value, /* field */ "port");
auto port = AccessPathTemplate::from_json(value["port"]);
if (!port.path().empty()) {
throw JsonValidationError(
value,
/* field */ "port",
/* expected */ "an access path root without field");
}
return AddFeaturesToArgumentsTemplate(features, port.root());
}
void AddFeaturesToArgumentsTemplate::instantiate(
const TemplateVariableMapping& parameter_positions,
Model& model) const {
model.add_add_features_to_arguments(
port_.instantiate(parameter_positions), features_);
}
ForAllParameters::ForAllParameters(
std::unique_ptr<AllOfTypeConstraint> constraints,
std::string variable,
std::vector<SinkTemplate> sink_templates,
std::vector<ParameterSourceTemplate> parameter_source_templates,
std::vector<GenerationTemplate> generation_templates,
std::vector<SourceTemplate> source_templates,
std::vector<PropagationTemplate> propagation_templates,
std::vector<AttachToSourcesTemplate> attach_to_sources_templates,
std::vector<AttachToSinksTemplate> attach_to_sinks_templates,
std::vector<AttachToPropagationsTemplate> attach_to_propagations_templates,
std::vector<AddFeaturesToArgumentsTemplate>
add_features_to_arguments_templates)
: constraints_(std::move(constraints)),
variable_(std::move(variable)),
sink_templates_(std::move(sink_templates)),
parameter_source_templates_(std::move(parameter_source_templates)),
generation_templates_(std::move(generation_templates)),
source_templates_(std::move(source_templates)),
propagation_templates_(std::move(propagation_templates)),
attach_to_sources_templates_(std::move(attach_to_sources_templates)),
attach_to_sinks_templates_(std::move(attach_to_sinks_templates)),
attach_to_propagations_templates_(
std::move(attach_to_propagations_templates)),
add_features_to_arguments_templates_(
std::move(add_features_to_arguments_templates)) {}
ForAllParameters ForAllParameters::from_json(
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
std::vector<std::unique_ptr<TypeConstraint>> constraints;
std::vector<SinkTemplate> sink_templates;
std::vector<ParameterSourceTemplate> parameter_source_templates;
std::vector<GenerationTemplate> generation_templates;
std::vector<SourceTemplate> source_templates;
std::vector<PropagationTemplate> propagation_templates;
std::vector<AttachToSourcesTemplate> attach_to_sources_templates;
std::vector<AttachToSinksTemplate> attach_to_sinks_templates;
std::vector<AttachToPropagationsTemplate> attach_to_propagations_templates;
std::vector<AddFeaturesToArgumentsTemplate>
add_features_to_arguments_templates;
std::string variable = JsonValidation::string(value, "variable");
for (auto constraint :
JsonValidation::null_or_array(value, /* field */ "where")) {
// We assume constraints on parameters are type constraints
constraints.push_back(TypeConstraint::from_json(constraint));
}
for (auto sink_value :
JsonValidation::null_or_array(value, /* field */ "sinks")) {
sink_templates.push_back(SinkTemplate::from_json(sink_value, context));
}
for (auto source_value :
JsonValidation::null_or_array(value, /* field */ "parameter_sources")) {
parameter_source_templates.push_back(
ParameterSourceTemplate::from_json(source_value, context));
}
for (auto source_value :
JsonValidation::null_or_array(value, /* field */ "generations")) {
generation_templates.push_back(
GenerationTemplate::from_json(source_value, context));
}
for (auto source_value :
JsonValidation::null_or_array(value, /* field */ "sources")) {
source_templates.push_back(
SourceTemplate::from_json(source_value, context));
}
for (auto propagation_value :
JsonValidation::null_or_array(value, /* field */ "propagation")) {
propagation_templates.push_back(
PropagationTemplate::from_json(propagation_value, context));
}
for (auto attach_to_sources_value :
JsonValidation::null_or_array(value, /* field */ "attach_to_sources")) {
attach_to_sources_templates.push_back(
AttachToSourcesTemplate::from_json(attach_to_sources_value, context));
}
for (auto attach_to_sinks_value :
JsonValidation::null_or_array(value, /* field */ "attach_to_sinks")) {
attach_to_sinks_templates.push_back(
AttachToSinksTemplate::from_json(attach_to_sinks_value, context));
}
for (auto attach_to_propagations_value : JsonValidation::null_or_array(
value, /* field */ "attach_to_propagations")) {
attach_to_propagations_templates.push_back(
AttachToPropagationsTemplate::from_json(
attach_to_propagations_value, context));
}
for (auto add_features_to_arguments_value : JsonValidation::null_or_array(
value, /* field */ "add_features_to_arguments")) {
add_features_to_arguments_templates.push_back(
AddFeaturesToArgumentsTemplate::from_json(
add_features_to_arguments_value, context));
}
return ForAllParameters(
std::make_unique<AllOfTypeConstraint>(std::move(constraints)),
variable,
sink_templates,
parameter_source_templates,
generation_templates,
source_templates,
propagation_templates,
attach_to_sources_templates,
attach_to_sinks_templates,
attach_to_propagations_templates,
add_features_to_arguments_templates);
}
bool ForAllParameters::instantiate(Model& model, const Method* method) const {
bool updated = false;
ParameterPosition index = method->first_parameter_index();
for (auto type : *method->get_proto()->get_args()) {
if (constraints_->satisfy(type)) {
LOG(3, "Type {} satifies constraints in for_all_parameters", show(type));
TemplateVariableMapping variable_mapping;
variable_mapping.insert(variable_, index);
for (const auto& sink_template : sink_templates_) {
sink_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& parameter_source_template :
parameter_source_templates_) {
parameter_source_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& generation_template : generation_templates_) {
generation_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& source_template : source_templates_) {
source_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& propagation_template : propagation_templates_) {
propagation_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& attach_to_sources_template :
attach_to_sources_templates_) {
attach_to_sources_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& attach_to_sinks_template : attach_to_sinks_templates_) {
attach_to_sinks_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& attach_to_propagations_template :
attach_to_propagations_templates_) {
attach_to_propagations_template.instantiate(variable_mapping, model);
updated = true;
}
for (const auto& add_features_to_arguments_template :
add_features_to_arguments_templates_) {
add_features_to_arguments_template.instantiate(variable_mapping, model);
updated = true;
}
}
index++;
}
return updated;
}
ModelTemplate::ModelTemplate(
const Model& model,
std::vector<ForAllParameters> for_all_parameters)
: model_(model), for_all_parameters_(std::move(for_all_parameters)) {
mt_assert(model.method() == nullptr);
}
std::optional<Model> ModelTemplate::instantiate(
Context& context,
const Method* method) const {
Model model = model_.instantiate(method, context);
bool updated = false;
for (const auto& for_all_parameters : for_all_parameters_) {
bool result = for_all_parameters.instantiate(model, method);
updated = updated || result;
}
// An instantiated model can be nonempty even when it is instantiated from
// an empty model and no new sinks/generations/propagations/sources were
// introduced by for_all_parameters, because it is possible that a model has
// non-zero propagations after instantiation (because Model constructor may
// add default propagations)
if (!model_.empty() || updated) {
return model;
} else {
LOG(3,
"Method {} generates no new sinks/generations/propagations/sources from {} for_all_parameters constraints:\nInstantiated model: {}.\nModel template: {}.",
method->show(),
for_all_parameters_.size(),
JsonValidation::to_styled_string(model.to_json()),
JsonValidation::to_styled_string(model_.to_json()));
return std::nullopt;
}
}
ModelTemplate ModelTemplate::from_json(
const Json::Value& model,
Context& context) {
JsonValidation::validate_object(model);
std::vector<ForAllParameters> for_all_parameters;
for (auto const& value :
JsonValidation::null_or_array(model, /* field */ "for_all_parameters")) {
for_all_parameters.push_back(ForAllParameters::from_json(value, context));
}
return ModelTemplate(
Model::from_json(
/* method */ nullptr, model, context),
std::move(for_all_parameters));
}
} // namespace marianatrench