source/Frame.cpp (447 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 <boost/functional/hash.hpp> #include "mariana-trench/Constants.h" #include <Show.h> #include <mariana-trench/Access.h> #include <mariana-trench/Frame.h> #include <mariana-trench/JsonValidation.h> namespace marianatrench { void Frame::set_origins(const MethodSet& origins) { origins_ = origins; } void Frame::set_field_origins(const FieldSet& field_origins) { field_origins_ = field_origins; } void Frame::add_inferred_features(const FeatureMayAlwaysSet& features) { locally_inferred_features_.add(features); } FeatureMayAlwaysSet Frame::features() const { auto features = inferred_features_; features.add(locally_inferred_features_); if (features.is_bottom()) { return FeatureMayAlwaysSet::make_always(user_features_); } features.add_always(user_features_); mt_assert(!features.is_bottom()); return features; } void Frame::add_local_position(const Position* position) { local_positions_.add(position); } void Frame::set_local_positions(LocalPositionSet positions) { mt_assert(!positions.is_bottom()); local_positions_ = std::move(positions); } bool Frame::leq(const Frame& other) const { if (is_bottom()) { return true; } else if (other.is_bottom()) { return false; } else { return kind_ == other.kind_ && (is_artificial_source() ? callee_port_.leq(other.callee_port_) : callee_port_ == other.callee_port_) && callee_ == other.callee_ && call_position_ == other.call_position_ && distance_ >= other.distance_ && origins_.leq(other.origins_) && field_origins_.leq(other.field_origins_) && inferred_features_.leq(other.inferred_features_) && locally_inferred_features_.leq(other.locally_inferred_features_) && user_features_.leq(other.user_features_) && via_type_of_ports_.leq(other.via_type_of_ports_) && via_value_of_ports_.leq(other.via_value_of_ports_) && local_positions_.leq(other.local_positions_) && canonical_names_.leq(other.canonical_names_); } } bool Frame::equals(const Frame& other) const { if (is_bottom()) { return other.is_bottom(); } else if (other.is_bottom()) { return false; } else { return kind_ == other.kind_ && callee_port_ == other.callee_port_ && callee_ == other.callee_ && call_position_ == other.call_position_ && distance_ == other.distance_ && origins_ == other.origins_ && field_origins_ == other.field_origins_ && inferred_features_ == other.inferred_features_ && locally_inferred_features_ == other.locally_inferred_features_ && user_features_ == other.user_features_ && via_type_of_ports_ == other.via_type_of_ports_ && via_value_of_ports_ == other.via_value_of_ports_ && local_positions_ == other.local_positions_ && canonical_names_ == other.canonical_names_; } } void Frame::join_with(const Frame& other) { mt_if_expensive_assert(auto previous = *this); if (is_bottom()) { *this = other; } else if (other.is_bottom()) { return; } else { mt_assert(kind_ == other.kind_); mt_assert(callee_ == other.callee_); mt_assert(call_position_ == other.call_position_); if (is_artificial_source()) { callee_port_.join_with(other.callee_port_); } else { mt_assert(callee_port_ == other.callee_port_); } distance_ = std::min(distance_, other.distance_); origins_.join_with(other.origins_); field_origins_.join_with(other.field_origins_); inferred_features_.join_with(other.inferred_features_); locally_inferred_features_.join_with(other.locally_inferred_features_); user_features_.join_with(other.user_features_); via_type_of_ports_.join_with(other.via_type_of_ports_); via_value_of_ports_.join_with(other.via_value_of_ports_); local_positions_.join_with(other.local_positions_); canonical_names_.join_with(other.canonical_names_); } mt_expensive_assert(previous.leq(*this) && other.leq(*this)); } void Frame::widen_with(const Frame& other) { join_with(other); } void Frame::meet_with(const Frame& /*other*/) { // Not implemented. } void Frame::narrow_with(const Frame& other) { meet_with(other); } Frame Frame::artificial_source(ParameterPosition parameter_position) { return Frame::artificial_source( AccessPath(Root(Root::Kind::Argument, parameter_position))); } Frame Frame::artificial_source(AccessPath access_path) { return Frame( /* kind */ Kinds::artificial_source(), /* callee_port */ access_path, /* callee */ nullptr, /* field_callee */ nullptr, /* call_position */ nullptr, /* distance */ 0, /* origins */ {}, /* field_origins */ {}, /* inferred_features */ {}, /* locally_inferred_features */ {}, /* user_features */ {}, /* via_type_of_ports */ {}, /* via_value_of_ports */ {}, /* local_positions */ {}, /* canonical_names */ {}); } void Frame::callee_port_append(Path::Element path_element) { callee_port_.append(path_element); } Frame Frame::with_kind(const Kind* kind) const { Frame new_frame(*this); new_frame.kind_ = kind; return new_frame; } AccessPath validate_and_infer_crtex_callee_port( const Json::Value& value, const AccessPath& callee_port, const CanonicalNameSetAbstractDomain& canonical_names, const RootSetAbstractDomain& via_type_of_ports) { mt_assert(canonical_names.is_value() && !canonical_names.elements().empty()); // Anchor ports only go with templated canonical names. Producer ports only // go with instantiated canonical names. No other ports are allowed. bool is_templated = false; bool is_instantiated = false; for (const auto& canonical_name : canonical_names.elements()) { if (canonical_name.instantiated_value()) { is_instantiated = true; } else { is_templated = true; } } if (is_instantiated == is_templated) { throw JsonValidationError( value, /* field */ "canonical_names", "all instantiated, or all templated values, not mix of both"); } if (is_templated) { auto num_via_type_of_ports = via_type_of_ports.is_value() ? via_type_of_ports.elements().size() : 0; for (const auto& canonical_name : canonical_names.elements()) { auto is_via_type_of = canonical_name.is_via_type_of_template(); if (is_via_type_of && num_via_type_of_ports != 1) { throw JsonValidationError( value, /* field */ std::nullopt, "exactly one 'via_type_of' port when canonical name contains 'via_type_of' template"); } } } // If callee_port is user-specified and not Leaf, validate it. if (callee_port.root().is_anchor() && is_instantiated) { throw JsonValidationError( value, /* field */ std::nullopt, "`Anchor` callee ports to go with templated canonical names."); } else if (callee_port.root().is_producer() && is_templated) { throw JsonValidationError( value, /* field */ std::nullopt, "`Producer` callee ports to go with instantiated canonical names."); } else if (!callee_port.root().is_leaf_port()) { throw JsonValidationError( value, /* field */ std::nullopt, "`Anchor` or `Producer` callee port for crtex frame with canonical_names defined."); } if (callee_port.root().is_leaf()) { if (is_instantiated) { throw JsonValidationError( value, /* field */ std::nullopt, "Instantiated canonical names must have callee_port defined as `Producer.<producer_id>.<canonical_port>`"); } // If the callee_port is defaulted to Leaf, it should be updated to an // Anchor to enable detection that this comes from a CRTEX producer. return AccessPath(Root(Root::Kind::Anchor)); } return callee_port; } Frame Frame::from_json(const Json::Value& value, Context& context) { JsonValidation::validate_object(value); const Kind* kind = Kind::from_json(value, context); auto callee_port = AccessPath(Root(Root::Kind::Leaf)); if (value.isMember("callee_port")) { JsonValidation::string(value, /* field */ "callee_port"); callee_port = AccessPath::from_json(value["callee_port"]); } const Method* callee = nullptr; if (value.isMember("callee")) { callee = Method::from_json( JsonValidation::object_or_string(value, /* field */ "callee"), context); } const Position* call_position = nullptr; if (value.isMember("call_position")) { call_position = Position::from_json( JsonValidation::object(value, /* field */ "call_position"), context); } int distance = 0; if (value.isMember("distance")) { distance = JsonValidation::integer(value, /* field */ "distance"); } JsonValidation::null_or_array(value, /* field */ "origins"); auto origins = MethodSet::from_json(value["origins"], context); JsonValidation::null_or_array(value, /* field */ "field_origins"); auto field_origins = FieldSet::from_json(value["field_origins"], context); // Inferred may_features and always_features. Technically, user-specified // 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); // User specified always-features. FeatureSet user_features; if (value.isMember("features")) { JsonValidation::null_or_array(value, /* field */ "features"); user_features = FeatureSet::from_json(value["features"], context); } RootSetAbstractDomain via_type_of_ports; if (value.isMember("via_type_of")) { for (const auto& root : JsonValidation::null_or_array(value, /* field */ "via_type_of")) { via_type_of_ports.add(Root::from_json(root)); } } RootSetAbstractDomain via_value_of_ports; if (value.isMember("via_value_of")) { for (const auto& root : JsonValidation::null_or_array(value, /* field */ "via_value_of")) { via_value_of_ports.add(Root::from_json(root)); } } LocalPositionSet local_positions; if (value.isMember("local_positions")) { JsonValidation::null_or_array(value, /* field */ "local_positions"); local_positions = LocalPositionSet::from_json(value["local_positions"], context); } CanonicalNameSetAbstractDomain canonical_names; if (value.isMember("canonical_names")) { for (const auto& canonical_name : JsonValidation::nonempty_array(value, /* field */ "canonical_names")) { canonical_names.add(CanonicalName::from_json(canonical_name)); } } if (canonical_names.is_value() && !canonical_names.elements().empty()) { callee_port = validate_and_infer_crtex_callee_port( value, callee_port, canonical_names, via_type_of_ports); } else if ( callee_port.root().is_anchor() || callee_port.root().is_producer()) { throw JsonValidationError( value, /* field */ std::nullopt, "canonical_names to be specified with `Anchor` or `Producer` callee_port."); } // Sanity checks. if (callee == nullptr) { if (!callee_port.root().is_leaf_port()) { throw JsonValidationError( value, /* field */ "callee_port", /* expected */ "`Leaf`, `Anchor` or `Producer`"); } else if (call_position != nullptr) { throw JsonValidationError( value, /* field */ "call_position", /* expected */ "unspecified position for leaf taint"); } else if (distance != 0) { throw JsonValidationError( value, /* field */ "distance", /* expected */ "a value of 0"); } } else { if (callee_port.root().is_leaf_port()) { throw JsonValidationError( value, /* field */ "callee_port", /* expected */ "`Argument(x)` or `Return`"); } else if (call_position == nullptr) { throw JsonValidationError( value, /* field */ "call_position", /* expected */ "non-null position"); } else if (distance == 0) { throw JsonValidationError( value, /* field */ "distance", /* expected */ "non-zero distance"); } } return Frame( kind, std::move(callee_port), callee, /* field_callee */ nullptr, // A field callee can never be set from a json // model generator call_position, distance, std::move(origins), std::move(field_origins), std::move(inferred_features), /* locally_inferred_features */ FeatureMayAlwaysSet::bottom(), std::move(user_features), std::move(via_type_of_ports), std::move(via_value_of_ports), std::move(local_positions), std::move(canonical_names)); } Json::Value Frame::to_json() const { auto value = Json::Value(Json::objectValue); mt_assert(kind_ != nullptr); auto kind_json = kind_->to_json(); mt_assert(kind_json.isObject()); for (const auto& member : kind_json.getMemberNames()) { value[member] = kind_json[member]; } value["callee_port"] = callee_port_.to_json(); if (callee_ != nullptr) { value["callee"] = callee_->to_json(); } else if (field_callee_ != nullptr) { value["field_callee"] = field_callee_->to_json(); } if (call_position_ != nullptr) { value["call_position"] = call_position_->to_json(); } if (distance_ != 0) { value["distance"] = Json::Value(distance_); } if (!origins_.empty()) { value["origins"] = origins_.to_json(); } if (!field_origins_.empty()) { value["field_origins"] = field_origins_.to_json(); } JsonValidation::update_object(value, features().to_json()); if (!locally_inferred_features_.is_bottom() && !locally_inferred_features_.empty()) { value["local_features"] = locally_inferred_features_.to_json(); } if (via_type_of_ports_.is_value() && !via_type_of_ports_.elements().empty()) { auto ports = Json::Value(Json::arrayValue); for (const auto& root : via_type_of_ports_.elements()) { ports.append(root.to_json()); } value["via_type_of"] = ports; } if (via_value_of_ports_.is_value() && !via_value_of_ports_.elements().empty()) { auto ports = Json::Value(Json::arrayValue); for (const auto& root : via_value_of_ports_.elements()) { ports.append(root.to_json()); } value["via_value_of"] = ports; } if (local_positions_.is_value() && !local_positions_.empty()) { value["local_positions"] = local_positions_.to_json(); } if (canonical_names_.is_value() && !canonical_names_.elements().empty()) { auto canonical_names = Json::Value(Json::arrayValue); for (const auto& canonical_name : canonical_names_.elements()) { canonical_names.append(canonical_name.to_json()); } value["canonical_names"] = canonical_names; } return value; } bool Frame::GroupEqual::operator()(const Frame& left, const Frame& right) const { return left.kind() == right.kind() && (left.is_artificial_source() ? left.callee_port().root() == right.callee_port().root() : left.callee_port() == right.callee_port()) && left.callee() == right.callee() && left.call_position() == right.call_position(); } std::size_t Frame::GroupHash::operator()(const Frame& frame) const { std::size_t seed = 0; boost::hash_combine(seed, frame.kind()); if (frame.is_artificial_source()) { boost::hash_combine(seed, frame.callee_port().root().encode()); } else { boost::hash_combine(seed, std::hash<AccessPath>()(frame.callee_port())); } boost::hash_combine(seed, frame.callee()); boost::hash_combine(seed, frame.call_position()); return seed; } std::ostream& operator<<(std::ostream& out, const Frame& frame) { out << "Frame(kind=`" << show(frame.kind_) << "`, callee_port=" << frame.callee_port_; if (frame.callee_ != nullptr) { out << ", callee=`" << show(frame.callee_) << "`"; } else if (frame.field_callee_ != nullptr) { out << ", field_callee=`" << show(frame.field_callee_) << "`"; } if (frame.call_position_ != nullptr) { out << ", call_position=" << show(frame.call_position_); } if (frame.distance_ != 0) { out << ", distance=" << frame.distance_; } if (!frame.origins_.empty()) { out << ", origins=" << frame.origins_; } if (!frame.field_origins_.empty()) { out << ", field_origins=" << frame.field_origins_; } if (!frame.inferred_features_.empty()) { out << ", inferred_features=" << frame.inferred_features_; } if (!frame.locally_inferred_features_.empty()) { out << ", locally_inferred_features=" << frame.locally_inferred_features_; } if (!frame.user_features_.empty()) { out << ", user_features=" << frame.user_features_; } if (frame.via_type_of_ports_.is_value() && !frame.via_type_of_ports_.elements().empty()) { out << ", via_type_of_ports=" << frame.via_type_of_ports_; } if (frame.via_value_of_ports_.is_value() && !frame.via_value_of_ports_.elements().empty()) { out << ", via_value_of_ports=" << frame.via_value_of_ports_; } if (!frame.local_positions_.empty()) { out << ", local_positions=" << frame.local_positions_; } if (frame.canonical_names_.is_value() && !frame.canonical_names_.elements().empty()) { out << ", canonical_names=" << frame.canonical_names_; } return out << ")"; } } // namespace marianatrench