source/Model.cpp (1,139 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 <fmt/format.h>
#include <Show.h>
#include <TypeUtil.h>
#include <mariana-trench/Assert.h>
#include <mariana-trench/ClassHierarchies.h>
#include <mariana-trench/ClassProperties.h>
#include <mariana-trench/Compiler.h>
#include <mariana-trench/Features.h>
#include <mariana-trench/FieldCache.h>
#include <mariana-trench/Heuristics.h>
#include <mariana-trench/JsonValidation.h>
#include <mariana-trench/Log.h>
#include <mariana-trench/Model.h>
#include <mariana-trench/Overrides.h>
#include <mariana-trench/Positions.h>
namespace marianatrench {
namespace {
class ModelConsistencyError {
public:
static void raise(const std::string& what) {
ERROR(1, "Model Consistency Error: {}", what);
}
};
} // namespace
std::string model_mode_to_string(Model::Mode mode) {
switch (mode) {
case Model::Mode::Normal:
return "normal";
case Model::Mode::OverrideDefault:
return "override-default";
case Model::Mode::SkipAnalysis:
return "skip-analysis";
case Model::Mode::AddViaObscureFeature:
return "add-via-obscure-feature";
case Model::Mode::TaintInTaintOut:
return "taint-in-taint-out";
case Model::Mode::TaintInTaintThis:
return "taint-in-taint-this";
case Model::Mode::NoJoinVirtualOverrides:
return "no-join-virtual-overrides";
case Model::Mode::NoCollapseOnPropagation:
return "no-collapse-on-propagation";
case Model::Mode::AliasMemoryLocationOnInvoke:
return "alias-memory-location-on-invoke";
default:
mt_unreachable();
}
}
std::optional<Model::Mode> string_to_model_mode(const std::string& mode) {
if (mode == "normal") {
return Model::Mode::Normal;
} else if (mode == "override-default") {
return Model::Mode::OverrideDefault;
} else if (mode == "skip-analysis") {
return Model::Mode::SkipAnalysis;
} else if (mode == "add-via-obscure-feature") {
return Model::Mode::AddViaObscureFeature;
} else if (mode == "taint-in-taint-out") {
return Model::Mode::TaintInTaintOut;
} else if (mode == "taint-in-taint-this") {
return Model::Mode::TaintInTaintThis;
} else if (mode == "no-join-virtual-overrides") {
return Model::Mode::NoJoinVirtualOverrides;
} else if (mode == "no-collapse-on-propagation") {
return Model::Mode::NoCollapseOnPropagation;
} else if (mode == "alias-memory-location-on-invoke") {
return Model::Mode::AliasMemoryLocationOnInvoke;
} else {
return std::nullopt;
}
}
Model::Model() : method_(nullptr) {}
Model::Model(
const Method* method,
Context& context,
Modes modes,
const std::vector<std::pair<AccessPath, Frame>>& generations,
const std::vector<std::pair<AccessPath, Frame>>& parameter_sources,
const std::vector<std::pair<AccessPath, Frame>>& sinks,
const std::vector<std::pair<Propagation, AccessPath>>& propagations,
const std::vector<Sanitizer>& global_sanitizers,
const std::vector<std::pair<Root, SanitizerSet>>& port_sanitizers,
const std::vector<std::pair<Root, FeatureSet>>& attach_to_sources,
const std::vector<std::pair<Root, FeatureSet>>& attach_to_sinks,
const std::vector<std::pair<Root, FeatureSet>>& attach_to_propagations,
const std::vector<std::pair<Root, FeatureSet>>& add_features_to_arguments,
const AccessPathConstantDomain& inline_as,
const IssueSet& issues)
: method_(method), modes_(modes) {
if (method_ && !modes_.test(Model::Mode::OverrideDefault)) {
// Use a set of heuristics to infer the modes of this method.
auto* code = method_->get_code();
if (!code) {
modes_ |= Model::Mode::SkipAnalysis;
modes_ |= Model::Mode::AddViaObscureFeature;
}
}
if (modes_.test(Model::Mode::TaintInTaintOut)) {
add_taint_in_taint_out(context);
}
if (modes_.test(Model::Mode::TaintInTaintThis)) {
add_taint_in_taint_this(context);
}
for (const auto& [port, source] : generations) {
add_generation(port, source);
}
for (const auto& [port, source] : parameter_sources) {
add_parameter_source(port, source);
}
for (const auto& [port, sink] : sinks) {
add_sink(port, sink);
}
for (const auto& [propagation, output] : propagations) {
add_propagation(propagation, output);
}
for (const auto& sanitizer : global_sanitizers) {
add_global_sanitizer(sanitizer);
}
for (const auto& [root, sanitizers] : port_sanitizers) {
add_port_sanitizers(sanitizers, root);
}
for (const auto& [root, features] : attach_to_sources) {
add_attach_to_sources(root, features);
}
for (const auto& [root, features] : attach_to_sinks) {
add_attach_to_sinks(root, features);
}
for (const auto& [root, features] : attach_to_propagations) {
add_attach_to_propagations(root, features);
}
for (const auto& [root, features] : add_features_to_arguments) {
add_add_features_to_arguments(root, features);
}
set_inline_as(inline_as);
for (const auto& issue : issues) {
add_issue(issue);
}
}
bool Model::operator==(const Model& other) const {
return modes_ == other.modes_ && generations_ == other.generations_ &&
parameter_sources_ == other.parameter_sources_ &&
sinks_ == other.sinks_ && propagations_ == other.propagations_ &&
global_sanitizers_ == other.global_sanitizers_ &&
port_sanitizers_ == other.port_sanitizers_ &&
attach_to_sources_ == other.attach_to_sources_ &&
attach_to_sinks_ == other.attach_to_sinks_ &&
attach_to_propagations_ == other.attach_to_propagations_ &&
add_features_to_arguments_ == other.add_features_to_arguments_ &&
inline_as_ == other.inline_as_ && issues_ == other.issues_;
}
bool Model::operator!=(const Model& other) const {
return !(*this == other);
}
Model Model::instantiate(const Method* method, Context& context) const {
Model model(method, context, modes_);
for (const auto& [port, generation_taint] : generations_.elements()) {
for (const auto& generations : generation_taint) {
for (const auto& generation : generations) {
model.add_generation(port, generation);
}
}
}
for (const auto& [port, parameter_source_taint] :
parameter_sources_.elements()) {
for (const auto& parameter_sources : parameter_source_taint) {
for (const auto& parameter_source : parameter_sources) {
model.add_parameter_source(port, parameter_source);
}
}
}
for (const auto& [port, sink_taint] : sinks_.elements()) {
for (const auto& sinks : sink_taint) {
for (const auto& sink : sinks) {
model.add_sink(port, sink);
}
}
}
for (const auto& [output, propagations] : propagations_.elements()) {
for (const auto& propagation : propagations) {
model.add_propagation(propagation, output);
}
}
for (const auto& sanitizer : global_sanitizers_) {
model.add_global_sanitizer(sanitizer);
}
for (const auto& [root, sanitizers] : port_sanitizers_) {
model.add_port_sanitizers(sanitizers, root);
}
for (const auto& [root, features] : attach_to_sources_) {
model.add_attach_to_sources(root, features);
}
for (const auto& [root, features] : attach_to_sinks_) {
model.add_attach_to_sinks(root, features);
}
for (const auto& [root, features] : attach_to_propagations_) {
model.add_attach_to_propagations(root, features);
}
for (const auto& [root, features] : add_features_to_arguments_) {
model.add_add_features_to_arguments(root, features);
}
model.set_inline_as(inline_as_);
return model;
}
Model Model::at_callsite(
const Method* caller,
const Position* call_position,
Context& context,
const std::vector<const DexType * MT_NULLABLE>& source_register_types,
const std::vector<std::optional<std::string>>& source_constant_arguments)
const {
const auto* callee = method_;
Model model;
model.modes_ = modes_;
auto maximum_source_sink_distance =
context.options->maximum_source_sink_distance();
// Add special features that cannot be done in model generators.
mt_assert(context.features != nullptr);
auto extra_features = context.class_properties->propagate_features(
caller, callee, *context.features);
generations_.visit(
[&model,
caller,
callee,
call_position,
maximum_source_sink_distance,
&extra_features,
&context,
&source_register_types,
&source_constant_arguments](
const AccessPath& callee_port, const Taint& generations) {
model.generations_.write(
callee_port,
generations.propagate(
caller,
callee,
callee_port,
call_position,
maximum_source_sink_distance,
extra_features,
context,
source_register_types,
source_constant_arguments),
UpdateKind::Weak);
});
sinks_.visit([&model,
caller,
callee,
call_position,
maximum_source_sink_distance,
&extra_features,
&context,
&source_register_types,
&source_constant_arguments](
const AccessPath& callee_port, const Taint& sinks) {
model.sinks_.write(
callee_port,
sinks.propagate(
caller,
callee,
callee_port,
call_position,
maximum_source_sink_distance,
extra_features,
context,
source_register_types,
source_constant_arguments),
UpdateKind::Weak);
});
model.propagations_ = propagations_;
model.add_features_to_arguments_ = add_features_to_arguments_;
model.inline_as_ = inline_as_;
if (inline_as_.is_bottom()) {
// This is bottom when the method was never analyzed.
// Set it to top to be sound when joining models.
model.inline_as_.set_to_top();
}
return model;
}
void Model::collapse_invalid_paths(Context& context) {
if (!method_) {
return;
}
using FieldTypesAccumulator = std::unordered_set<const DexType*>;
auto is_valid = [this, &context](
const FieldTypesAccumulator& previous_field_types,
Path::Element field) {
FieldTypesAccumulator current_field_types;
for (const auto* previous_field_type : previous_field_types) {
if (previous_field_type == type::java_lang_Object()) {
// Object is too generic to determine the set of possible field names.
continue;
}
const auto& cached_types =
context.field_cache->field_types(previous_field_type, field);
current_field_types.insert(cached_types.begin(), cached_types.end());
}
if (current_field_types.empty()) {
LOG(5,
"Model for method `{}` has invalid path element `{}`",
show(method_),
show(field));
return std::make_pair(false, current_field_types);
}
return std::make_pair(true, current_field_types);
};
auto initial_accumulator = [this](const Root& root) {
// Leaf ports appear in callee ports. This only applies to caller ports.
mt_assert(!root.is_leaf_port());
DexType* MT_NULLABLE root_type = nullptr;
if (root.is_argument()) {
root_type = method_->parameter_type(root.parameter_position());
} else if (root.is_return()) {
root_type = method_->return_type();
}
if (root_type == nullptr) {
// This can happen when there is an invalid model, e.g. model defined
// on an argument that does not exist, usually from a model-generator.
// Returning an empty list of types will collapse all paths.
ERROR(
1,
"Could not find root type for method `{}`, root: `{}`",
show(method_),
show(root));
return FieldTypesAccumulator{};
}
return FieldTypesAccumulator{root_type};
};
generations_.collapse_invalid_paths<FieldTypesAccumulator>(
is_valid, initial_accumulator);
parameter_sources_.collapse_invalid_paths<FieldTypesAccumulator>(
is_valid, initial_accumulator);
sinks_.collapse_invalid_paths<FieldTypesAccumulator>(
is_valid, initial_accumulator);
propagations_.collapse_invalid_paths<FieldTypesAccumulator>(
is_valid, initial_accumulator);
}
void Model::approximate() {
generations_.limit_leaves(Heuristics::kModelTreeMaxLeaves);
parameter_sources_.limit_leaves(Heuristics::kModelTreeMaxLeaves);
sinks_.limit_leaves(Heuristics::kModelTreeMaxLeaves);
propagations_.limit_leaves(Heuristics::kModelTreeMaxLeaves);
}
bool Model::empty() const {
return modes_.empty() && generations_.is_bottom() &&
parameter_sources_.is_bottom() && sinks_.is_bottom() &&
propagations_.is_bottom() && global_sanitizers_.is_bottom() &&
port_sanitizers_.is_bottom() && attach_to_sources_.is_bottom() &&
attach_to_sinks_.is_bottom() && attach_to_propagations_.is_bottom() &&
add_features_to_arguments_.is_bottom() && inline_as_.is_bottom() &&
issues_.is_bottom();
}
bool Model::check_root_consistency(Root root) const {
switch (root.kind()) {
case Root::Kind::Return: {
if (method_ && method_->returns_void()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains a `Return` port but method returns void.",
show(method_)));
return false;
}
return true;
}
case Root::Kind::Argument: {
auto position = root.parameter_position();
if (method_ && position >= method_->number_of_parameters()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains a port on parameter {} but the method only has {} parameters.",
show(method_),
position,
method_->number_of_parameters()));
return false;
}
return true;
}
default: {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains an invalid port: `{}`",
show(method_),
show(root)));
return false;
}
}
}
bool Model::check_port_consistency(const AccessPath& access_path) const {
return check_root_consistency(access_path.root());
}
bool Model::check_frame_consistency(const Frame& frame, std::string_view kind)
const {
if (frame.is_bottom()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains a bottom {}.", show(method_), kind));
return false;
}
if (frame.is_artificial_source()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains an artificial {}.",
show(method_),
kind));
return false;
}
if (method_ && (frame.origins().empty() && frame.field_origins().empty())) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains a {} without origins.",
show(method_),
kind));
return false;
}
if (frame.via_type_of_ports().is_value()) {
for (const auto& root : frame.via_type_of_ports().elements()) {
// Logs invalid ports specifed for via_type_of but does not prevent the
// model from being created.
check_port_consistency(AccessPath(root));
}
}
if (frame.via_value_of_ports().is_value()) {
for (const auto& root : frame.via_value_of_ports().elements()) {
// Logs invalid ports specifed for via_value_of but does not prevent the
// model from being created.
check_port_consistency(AccessPath(root));
}
}
return true;
}
bool Model::check_parameter_source_port_consistency(
const AccessPath& access_path) const {
if (access_path.root().is_return()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` contains a parameter source with a `Return` port."
" Use a generation instead.",
show(method_)));
return false;
}
return true;
}
bool Model::check_propagation_consistency(
const Propagation& propagation) const {
return check_port_consistency(propagation.input());
}
bool Model::check_inline_as_consistency(
const AccessPathConstantDomain& inline_as) const {
auto access_path = inline_as.get_constant();
if (!access_path) {
return true;
}
if (!access_path->root().is_argument()) {
ModelConsistencyError::raise(fmt::format(
"Model for method `{}` has an inline-as with a non-argument root.",
show(method_)));
return false;
}
return check_port_consistency(*access_path);
}
void Model::add_mode(Model::Mode mode, Context& context) {
mt_assert(mode != Model::Mode::OverrideDefault);
modes_ |= mode;
if (mode == Model::Mode::TaintInTaintOut ||
(mode == Model::Mode::AddViaObscureFeature &&
modes_.test(Model::Mode::TaintInTaintOut))) {
add_taint_in_taint_out(context);
}
if (mode == Model::Mode::TaintInTaintThis ||
(mode == Model::Mode::AddViaObscureFeature &&
modes_.test(Model::Mode::TaintInTaintThis))) {
add_taint_in_taint_this(context);
}
}
void Model::add_taint_in_taint_out(Context& context) {
modes_ |= Model::Mode::TaintInTaintOut;
if (!method_ || method_->returns_void()) {
return;
}
auto user_features = FeatureSet::bottom();
if (modes_.test(Model::Mode::AddViaObscureFeature)) {
user_features.add(context.features->get("via-obscure"));
user_features.add(context.features->get("via-obscure-taint-in-taint-out"));
}
for (ParameterPosition parameter_position = 0;
parameter_position < method_->number_of_parameters();
parameter_position++) {
add_propagation(
Propagation(
/* input */
AccessPath(Root(Root::Kind::Argument, parameter_position)),
/* inferred_features */ FeatureMayAlwaysSet::bottom(),
user_features),
/* output */ AccessPath(Root(Root::Kind::Return)));
}
}
void Model::add_taint_in_taint_this(Context& context) {
modes_ |= Model::Mode::TaintInTaintThis;
if (!method_ || method_->is_static()) {
return;
}
auto user_features = FeatureSet::bottom();
if (modes_.test(Model::Mode::AddViaObscureFeature)) {
user_features.add(context.features->get("via-obscure"));
}
for (ParameterPosition parameter_position = 1;
parameter_position < method_->number_of_parameters();
parameter_position++) {
add_propagation(
Propagation(
/* input */
AccessPath(Root(Root::Kind::Argument, parameter_position)),
/* inferred_features */ FeatureMayAlwaysSet::bottom(),
user_features),
/* output */ AccessPath(Root(Root::Kind::Argument, 0)));
}
}
namespace {
void update_taint_tree(
const Model* model,
TaintAccessPathTree& tree,
AccessPath port,
std::size_t truncation_amount,
Taint new_taint) {
if (!model->check_port_consistency(port)) {
return;
}
port.truncate(truncation_amount);
tree.write(port, std::move(new_taint), UpdateKind::Weak);
}
} // namespace
void Model::add_generation(AccessPath port, Frame source) {
if (method_ && source.origins().empty() && source.is_leaf()) {
source.set_origins(MethodSet{method_});
}
if (!check_port_consistency(port) ||
!check_frame_consistency(source, "source")) {
return;
}
port.truncate(Heuristics::kGenerationMaxPortSize);
generations_.write(port, Taint{std::move(source)}, UpdateKind::Weak);
}
void Model::add_generations(AccessPath port, Taint generations) {
update_taint_tree(
this,
generations_,
port,
Heuristics::kGenerationMaxPortSize,
generations);
}
void Model::add_inferred_generations(AccessPath port, Taint generations) {
auto sanitized_generations = apply_source_sink_sanitizers(
SanitizerKind::Sources, generations, port.root());
if (!sanitized_generations.is_bottom()) {
add_generations(port, sanitized_generations);
}
}
void Model::add_parameter_source(AccessPath port, Frame source) {
if (method_ && source.origins().empty() && source.is_leaf()) {
source.set_origins(MethodSet{method_});
}
if (!check_port_consistency(port) ||
!check_parameter_source_port_consistency(port) ||
!check_frame_consistency(source, "source")) {
return;
}
port.truncate(Heuristics::kParameterSourceMaxPortSize);
parameter_sources_.write(port, Taint{std::move(source)}, UpdateKind::Weak);
}
void Model::add_sink(AccessPath port, Frame sink) {
if (method_ && sink.origins().empty() && sink.is_leaf()) {
sink.set_origins(MethodSet{method_});
}
if (!check_port_consistency(port) || !check_frame_consistency(sink, "sink")) {
return;
}
port.truncate(Heuristics::kSinkMaxPortSize);
sinks_.write(port, Taint{std::move(sink)}, UpdateKind::Weak);
}
void Model::add_sinks(AccessPath port, Taint sinks) {
update_taint_tree(this, sinks_, port, Heuristics::kSinkMaxPortSize, sinks);
}
void Model::add_inferred_sinks(AccessPath port, Taint sinks) {
auto sanitized_sinks =
apply_source_sink_sanitizers(SanitizerKind::Sinks, sinks, port.root());
if (!sanitized_sinks.is_bottom()) {
add_sinks(port, sanitized_sinks);
}
}
void Model::add_propagation(Propagation propagation, AccessPath output) {
if (!check_propagation_consistency(propagation) ||
!check_port_consistency(output)) {
return;
}
output.truncate(Heuristics::kPropagationMaxPathSize);
propagation.truncate(Heuristics::kPropagationMaxPathSize);
propagations_.write(
output, PropagationSet{std::move(propagation)}, UpdateKind::Weak);
}
void Model::add_inferred_propagation(
Propagation propagation,
AccessPath output) {
if (has_global_propagation_sanitizer() ||
!port_sanitizers_.get(propagation.input().root()).is_bottom()) {
return;
}
add_propagation(propagation, output);
}
void Model::add_global_sanitizer(Sanitizer sanitizer) {
global_sanitizers_.add(sanitizer);
}
void Model::add_port_sanitizers(SanitizerSet sanitizers, Root root) {
if (!check_root_consistency(root)) {
return;
}
port_sanitizers_.update(
root, [&](const SanitizerSet& set) { return set.join(sanitizers); });
}
Taint Model::apply_source_sink_sanitizers(
SanitizerKind kind,
Taint taint,
Root root) {
mt_assert(kind != SanitizerKind::Propagations);
for (const auto& sanitizer_set :
{global_sanitizers_, port_sanitizers_.get(root)}) {
for (const auto& sanitizer : sanitizer_set) {
if (sanitizer.sanitizer_kind() == kind) {
if (sanitizer.kinds().is_top()) {
return Taint::bottom();
}
taint = taint.transform_kind_with_features(
[&sanitizer](const Kind* kind) -> std::vector<const Kind*> {
if (sanitizer.kinds().contains(kind)) {
return {};
}
return {kind};
},
/* add_features, never called */ nullptr);
}
}
}
return taint;
}
bool Model::has_global_propagation_sanitizer() {
return global_sanitizers_.contains(
Sanitizer(SanitizerKind::Propagations, KindSetAbstractDomain::top()));
}
void Model::add_attach_to_sources(Root root, FeatureSet features) {
if (!check_root_consistency(root)) {
return;
}
attach_to_sources_.update(
root, [&](const FeatureSet& set) { return set.join(features); });
}
FeatureSet Model::attach_to_sources(Root root) const {
return attach_to_sources_.get(root);
}
void Model::add_attach_to_sinks(Root root, FeatureSet features) {
if (!check_root_consistency(root)) {
return;
}
attach_to_sinks_.update(
root, [&](const FeatureSet& set) { return set.join(features); });
}
FeatureSet Model::attach_to_sinks(Root root) const {
return attach_to_sinks_.get(root);
}
void Model::add_attach_to_propagations(Root root, FeatureSet features) {
if (!check_root_consistency(root)) {
return;
}
attach_to_propagations_.update(
root, [&](const FeatureSet& set) { return set.join(features); });
}
FeatureSet Model::attach_to_propagations(Root root) const {
return attach_to_propagations_.get(root);
}
void Model::add_add_features_to_arguments(Root root, FeatureSet features) {
if (!check_root_consistency(root)) {
return;
}
add_attach_to_sources(root, features);
add_attach_to_sinks(root, features);
add_attach_to_propagations(root, features);
add_features_to_arguments_.update(
root, [&](const FeatureSet& set) { return set.join(features); });
}
bool Model::has_add_features_to_arguments() const {
return !add_features_to_arguments_.is_bottom();
}
FeatureSet Model::add_features_to_arguments(Root root) const {
return add_features_to_arguments_.get(root);
}
const AccessPathConstantDomain& Model::inline_as() const {
return inline_as_;
}
void Model::set_inline_as(AccessPathConstantDomain inline_as) {
if (!check_inline_as_consistency(inline_as)) {
return;
}
inline_as_ = std::move(inline_as);
}
void Model::add_issue(Issue trace) {
issues_.add(std::move(trace));
}
bool Model::override_default() const {
return modes_.test(Model::Mode::OverrideDefault);
}
bool Model::skip_analysis() const {
return modes_.test(Model::Mode::SkipAnalysis);
}
bool Model::add_via_obscure_feature() const {
return modes_.test(Model::Mode::AddViaObscureFeature);
}
bool Model::is_taint_in_taint_out() const {
return modes_.test(Model::Mode::TaintInTaintOut);
}
bool Model::is_taint_in_taint_this() const {
return modes_.test(Model::Mode::TaintInTaintThis);
}
bool Model::no_join_virtual_overrides() const {
return modes_.test(Model::Mode::NoJoinVirtualOverrides);
}
bool Model::no_collapse_on_propagation() const {
return modes_.test(Model::Mode::NoCollapseOnPropagation);
}
bool Model::alias_memory_location_on_invoke() const {
return modes_.test(Model::Mode::AliasMemoryLocationOnInvoke);
}
bool Model::leq(const Model& other) const {
return modes_.is_subset_of(other.modes_) &&
generations_.leq(other.generations_) &&
parameter_sources_.leq(other.parameter_sources_) &&
sinks_.leq(other.sinks_) && propagations_.leq(other.propagations_) &&
global_sanitizers_.leq(other.global_sanitizers_) &&
port_sanitizers_.leq(other.port_sanitizers_) &&
attach_to_sources_.leq(other.attach_to_sources_) &&
attach_to_sinks_.leq(other.attach_to_sinks_) &&
attach_to_propagations_.leq(other.attach_to_propagations_) &&
add_features_to_arguments_.leq(other.add_features_to_arguments_) &&
inline_as_.leq(other.inline_as_) && issues_.leq(other.issues_);
}
void Model::join_with(const Model& other) {
if (this == &other) {
return;
}
mt_if_expensive_assert(auto previous = *this);
modes_ |= other.modes_;
generations_.join_with(other.generations_);
parameter_sources_.join_with(other.parameter_sources_);
sinks_.join_with(other.sinks_);
propagations_.join_with(other.propagations_);
global_sanitizers_.join_with(other.global_sanitizers_);
port_sanitizers_.join_with(other.port_sanitizers_);
attach_to_sources_.join_with(other.attach_to_sources_);
attach_to_sinks_.join_with(other.attach_to_sinks_);
attach_to_propagations_.join_with(other.attach_to_propagations_);
add_features_to_arguments_.join_with(other.add_features_to_arguments_);
inline_as_.join_with(other.inline_as_);
issues_.join_with(other.issues_);
mt_expensive_assert(previous.leq(*this) && other.leq(*this));
}
Model Model::from_json(
const Method* method,
const Json::Value& value,
Context& context) {
JsonValidation::validate_object(value);
Modes modes;
for (auto mode_value :
JsonValidation::null_or_array(value, /* field */ "modes")) {
auto mode = string_to_model_mode(JsonValidation::string(mode_value));
if (!mode) {
throw JsonValidationError(value, /* field */ "modes", "valid mode");
}
modes.set(*mode, true);
}
Model model(method, context, modes);
for (auto generation_value :
JsonValidation::null_or_array(value, /* field */ "generations")) {
auto port = AccessPath(Root(Root::Kind::Return));
if (generation_value.isMember("port")) {
JsonValidation::string(generation_value, /* field */ "port");
port = AccessPath::from_json(generation_value["port"]);
} else if (generation_value.isMember("caller_port")) {
JsonValidation::string(generation_value, /* field */ "caller_port");
port = AccessPath::from_json(generation_value["caller_port"]);
}
model.add_generation(port, Frame::from_json(generation_value, context));
}
for (auto parameter_source_value :
JsonValidation::null_or_array(value, /* field */ "parameter_sources")) {
std::string port_field =
parameter_source_value.isMember("port") ? "port" : "caller_port";
JsonValidation::string(parameter_source_value, /* field */ port_field);
auto port = AccessPath::from_json(parameter_source_value[port_field]);
model.add_parameter_source(
port, Frame::from_json(parameter_source_value, context));
}
for (auto source_value :
JsonValidation::null_or_array(value, /* field */ "sources")) {
auto port = AccessPath(Root(Root::Kind::Return));
if (source_value.isMember("port")) {
JsonValidation::string(source_value, /* field */ "port");
port = AccessPath::from_json(source_value["port"]);
} else if (source_value.isMember("caller_port")) {
JsonValidation::string(source_value, /* field */ "caller_port");
port = AccessPath::from_json(source_value["caller_port"]);
}
auto source = Frame::from_json(source_value, context);
if (port.root().is_argument()) {
model.add_parameter_source(port, source);
} else {
model.add_generation(port, source);
}
}
for (auto sink_value :
JsonValidation::null_or_array(value, /* field */ "sinks")) {
std::string port_field =
sink_value.isMember("port") ? "port" : "caller_port";
JsonValidation::string(sink_value, /* field */ port_field);
auto port = AccessPath::from_json(sink_value[port_field]);
model.add_sink(port, Frame::from_json(sink_value, context));
}
for (auto propagation_value :
JsonValidation::null_or_array(value, /* field */ "propagation")) {
JsonValidation::string(propagation_value, /* field */ "output");
auto output = AccessPath::from_json(propagation_value["output"]);
model.add_propagation(
Propagation::from_json(propagation_value, context), output);
}
for (auto sanitizer_value :
JsonValidation::null_or_array(value, /* field */ "sanitizers")) {
auto sanitizer = Sanitizer::from_json(sanitizer_value, context);
if (!sanitizer_value.isMember("port")) {
model.add_global_sanitizer(sanitizer);
} else {
auto root = Root::from_json(sanitizer_value["port"]);
model.add_port_sanitizers(SanitizerSet(sanitizer), root);
}
}
for (auto attach_to_sources_value :
JsonValidation::null_or_array(value, /* field */ "attach_to_sources")) {
JsonValidation::string(attach_to_sources_value, /* field */ "port");
auto root = Root::from_json(attach_to_sources_value["port"]);
JsonValidation::null_or_array(
attach_to_sources_value, /* field */ "features");
auto features =
FeatureSet::from_json(attach_to_sources_value["features"], context);
model.add_attach_to_sources(root, features);
}
for (auto attach_to_sinks_value :
JsonValidation::null_or_array(value, /* field */ "attach_to_sinks")) {
JsonValidation::string(attach_to_sinks_value, /* field */ "port");
auto root = Root::from_json(attach_to_sinks_value["port"]);
JsonValidation::null_or_array(
attach_to_sinks_value, /* field */ "features");
auto features =
FeatureSet::from_json(attach_to_sinks_value["features"], context);
model.add_attach_to_sinks(root, features);
}
for (auto attach_to_propagations_value : JsonValidation::null_or_array(
value, /* field */ "attach_to_propagations")) {
JsonValidation::string(attach_to_propagations_value, /* field */ "port");
auto root = Root::from_json(attach_to_propagations_value["port"]);
JsonValidation::null_or_array(
attach_to_propagations_value, /* field */ "features");
auto features = FeatureSet::from_json(
attach_to_propagations_value["features"], context);
model.add_attach_to_propagations(root, features);
}
for (auto add_features_to_arguments_value : JsonValidation::null_or_array(
value, /* field */ "add_features_to_arguments")) {
JsonValidation::string(add_features_to_arguments_value, /* field */ "port");
auto root = Root::from_json(add_features_to_arguments_value["port"]);
JsonValidation::null_or_array(
add_features_to_arguments_value, /* field */ "features");
auto features = FeatureSet::from_json(
add_features_to_arguments_value["features"], context);
model.add_add_features_to_arguments(root, features);
}
if (value.isMember("inline_as")) {
JsonValidation::string(value, /* field */ "inline_as");
model.set_inline_as(
AccessPathConstantDomain(AccessPath::from_json(value["inline_as"])));
}
// We cannot parse issues for now.
if (value.isMember("issues")) {
throw JsonValidationError(
value, /* field */ std::nullopt, /* expected */ "model without issues");
}
return model;
}
Json::Value Model::to_json() const {
auto value = Json::Value(Json::objectValue);
if (method_) {
value["method"] = method_->to_json();
}
if (modes_) {
auto modes = Json::Value(Json::arrayValue);
for (auto mode : k_all_modes) {
if (modes_.test(mode)) {
modes.append(Json::Value(model_mode_to_string(mode)));
}
}
value["modes"] = modes;
}
if (!generations_.is_bottom()) {
auto generations_value = Json::Value(Json::arrayValue);
for (const auto& [port, generation_taint] : generations_.elements()) {
for (const auto& generations : generation_taint) {
for (const auto& generation : generations) {
mt_assert(!generation.is_bottom());
auto generation_value = generation.to_json();
generation_value["caller_port"] = port.to_json();
generations_value.append(generation_value);
}
}
}
value["generations"] = generations_value;
}
if (!parameter_sources_.is_bottom()) {
auto parameter_sources_value = Json::Value(Json::arrayValue);
for (const auto& [port, parameter_source_taint] :
parameter_sources_.elements()) {
for (const auto& parameter_sources : parameter_source_taint) {
for (const auto& parameter_source : parameter_sources) {
mt_assert(!parameter_source.is_bottom());
auto parameter_source_value = parameter_source.to_json();
parameter_source_value["caller_port"] = port.to_json();
parameter_sources_value.append(parameter_source_value);
}
}
}
value["parameter_sources"] = parameter_sources_value;
}
if (!sinks_.is_bottom()) {
auto sinks_value = Json::Value(Json::arrayValue);
for (const auto& [port, sink_taint] : sinks_.elements()) {
for (const auto& sinks : sink_taint) {
for (const auto& sink : sinks) {
mt_assert(!sink.is_bottom());
auto sink_value = sink.to_json();
sink_value["caller_port"] = port.to_json();
sinks_value.append(sink_value);
}
}
}
value["sinks"] = sinks_value;
}
if (!propagations_.is_bottom()) {
auto propagations_value = Json::Value(Json::arrayValue);
for (const auto& [output, propagations] : propagations_.elements()) {
for (const auto& propagation : propagations) {
auto propagation_value = propagation.to_json();
propagation_value["output"] = output.to_json();
propagations_value.append(propagation_value);
}
}
value["propagation"] = propagations_value;
}
auto sanitizers_value = Json::Value(Json::arrayValue);
for (const auto& sanitizer : global_sanitizers_) {
if (!sanitizer.is_bottom()) {
sanitizers_value.append(sanitizer.to_json());
}
}
for (const auto& [root, sanitizers] : port_sanitizers_) {
auto root_value = root.to_json();
for (const auto& sanitizer : sanitizers) {
if (!sanitizer.is_bottom()) {
auto sanitizer_value = sanitizer.to_json();
sanitizer_value["port"] = root_value;
sanitizers_value.append(sanitizer_value);
}
}
}
if (!sanitizers_value.empty()) {
value["sanitizers"] = sanitizers_value;
}
if (!attach_to_sources_.is_bottom()) {
auto attach_to_sources_value = Json::Value(Json::arrayValue);
for (const auto& [root, features] : attach_to_sources_) {
auto attach_to_sources_root_value = Json::Value(Json::objectValue);
attach_to_sources_root_value["port"] = root.to_json();
attach_to_sources_root_value["features"] = features.to_json();
attach_to_sources_value.append(attach_to_sources_root_value);
}
value["attach_to_sources"] = attach_to_sources_value;
}
if (!attach_to_sinks_.is_bottom()) {
auto attach_to_sinks_value = Json::Value(Json::arrayValue);
for (const auto& [root, features] : attach_to_sinks_) {
auto attach_to_sinks_root_value = Json::Value(Json::objectValue);
attach_to_sinks_root_value["port"] = root.to_json();
attach_to_sinks_root_value["features"] = features.to_json();
attach_to_sinks_value.append(attach_to_sinks_root_value);
}
value["attach_to_sinks"] = attach_to_sinks_value;
}
if (!attach_to_propagations_.is_bottom()) {
auto attach_to_propagations_value = Json::Value(Json::arrayValue);
for (const auto& [root, features] : attach_to_propagations_) {
auto attach_to_propagations_root_value = Json::Value(Json::objectValue);
attach_to_propagations_root_value["port"] = root.to_json();
attach_to_propagations_root_value["features"] = features.to_json();
attach_to_propagations_value.append(attach_to_propagations_root_value);
}
value["attach_to_propagations"] = attach_to_propagations_value;
}
if (!add_features_to_arguments_.is_bottom()) {
auto add_features_to_arguments_value = Json::Value(Json::arrayValue);
for (const auto& [root, features] : add_features_to_arguments_) {
auto add_features_to_arguments_root_value =
Json::Value(Json::objectValue);
add_features_to_arguments_root_value["port"] = root.to_json();
add_features_to_arguments_root_value["features"] = features.to_json();
add_features_to_arguments_value.append(
add_features_to_arguments_root_value);
}
value["add_features_to_arguments"] = add_features_to_arguments_value;
}
if (auto access_path = inline_as_.get_constant()) {
value["inline_as"] = access_path->to_json();
}
if (!issues_.is_bottom()) {
auto issues_value = Json::Value(Json::arrayValue);
for (const auto& issue : issues_) {
mt_assert(!issue.is_bottom());
issues_value.append(issue.to_json());
}
value["issues"] = issues_value;
}
return value;
}
Json::Value Model::to_json(Context& context) const {
auto value = to_json();
if (method_) {
const auto* position = context.positions->get(method_);
value["position"] = position->to_json();
}
return value;
}
std::ostream& operator<<(std::ostream& out, const Model& model) {
out << "\nModel(method=`" << show(model.method_) << "`";
if (model.modes_) {
out << ",\n modes={";
for (auto mode : k_all_modes) {
if (model.modes_.test(mode)) {
out << " " << model_mode_to_string(mode);
}
}
out << "}";
}
if (!model.generations_.is_bottom()) {
out << ",\n generations={\n";
for (const auto& [port, generation_taint] : model.generations_.elements()) {
for (const auto& generations : generation_taint) {
for (const auto& generation : generations) {
out << " " << port << ": " << generation << ",\n";
}
}
}
out << " }";
}
if (!model.parameter_sources_.is_bottom()) {
out << ",\n parameter_sources={\n";
for (const auto& [port, parameter_source_taint] :
model.parameter_sources_.elements()) {
for (const auto& parameter_sources : parameter_source_taint) {
for (const auto& parameter_source : parameter_sources) {
out << " " << port << ": " << parameter_source << ",\n";
}
}
}
out << " }";
}
if (!model.sinks_.is_bottom()) {
out << ",\n sinks={\n";
for (const auto& [port, sink_taint] : model.sinks_.elements()) {
for (const auto& sinks : sink_taint) {
for (const auto& sink : sinks) {
out << " " << port << ": " << sink << ",\n";
}
}
}
out << " }";
}
if (!model.propagations_.is_bottom()) {
out << ",\n propagation={\n";
for (const auto& [output, propagations] : model.propagations_.elements()) {
for (const auto& propagation : propagations) {
out << " " << propagation << " -> " << output << ",\n";
}
}
out << " }";
}
if (!model.global_sanitizers_.is_bottom()) {
out << ",\n global_sanitizers=" << model.global_sanitizers_;
}
if (!model.port_sanitizers_.is_bottom()) {
out << ",\n port_sanitizers={\n";
for (const auto& [root, sanitizers] : model.port_sanitizers_) {
out << " " << root << " -> " << sanitizers << ",\n";
}
out << " }";
}
if (!model.attach_to_sources_.is_bottom()) {
out << ",\n attach_to_sources={\n";
for (const auto& [root, features] : model.attach_to_sources_) {
out << " " << root << " -> " << features << ",\n";
}
out << " }";
}
if (!model.attach_to_sinks_.is_bottom()) {
out << ",\n attach_to_sinks={\n";
for (const auto& [root, features] : model.attach_to_sinks_) {
out << " " << root << " -> " << features << ",\n";
}
out << " }";
}
if (!model.attach_to_propagations_.is_bottom()) {
out << ",\n attach_to_propagations={\n";
for (const auto& [root, features] : model.attach_to_propagations_) {
out << " " << root << " -> " << features << ",\n";
}
out << " }";
}
if (!model.add_features_to_arguments_.is_bottom()) {
out << ",\n add_features_to_arguments={\n";
for (const auto& [root, features] : model.add_features_to_arguments_) {
out << " " << root << " -> " << features << ",\n";
}
out << " }";
}
if (auto access_path = model.inline_as_.get_constant()) {
out << ",\n inline_as=" << *access_path;
}
if (!model.issues_.is_bottom()) {
out << ",\n issues={\n";
for (const auto& issue : model.issues_) {
out << " " << issue << ",\n";
}
out << " }";
}
return out << ")";
}
void Model::remove_kinds(const std::unordered_set<const Kind*>& to_remove) {
auto drop_special_kinds =
[&to_remove](const Kind* kind) -> std::vector<const Kind*> {
if (to_remove.find(kind) != to_remove.end()) {
return std::vector<const Kind*>();
}
return std::vector<const Kind*>{kind};
};
auto map = [&drop_special_kinds](Taint& taint) -> void {
taint = taint.transform_kind_with_features(
drop_special_kinds, /* add_features, never called */ nullptr);
};
generations_.map(map);
parameter_sources_.map(map);
sinks_.map(map);
}
} // namespace marianatrench