include/DisjointUnionAbstractDomain.h (240 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. */ #pragma once #include <boost/optional.hpp> #include <boost/variant.hpp> #include <sstream> #include "AbstractDomain.h" // Forward declarations. namespace sparta { template <typename FirstDomain, typename... Domains> class DisjointUnionAbstractDomain; } // namespace sparta template <typename... Domains> std::ostream& operator<<( std::ostream&, const sparta::DisjointUnionAbstractDomain<Domains...>&); namespace sparta { /* * The disjoint union of abstract domains D1 ... Dn can hold any one of those * n domains. The join and meet of different domains is always Top and Bottom * respectively. * * In this paper[*], this construction is called the cardinal sum * (remark 10.1.10.4 at the top right corner of page 281). The cardinal sum is * endowed with two new extremal elements (i.e. Top and Bottom). In our * implementation, we treat these extremal elements as equivalent to the * respective extremal elements of the component domains. This construction is * meant for use cases where the component abstract domains have mostly disjoint * denotations. Hence, coalescing the extremal elements shouldn't have any * impact on the precision of the analysis in practice. (If the domains were * not disjoint, coalescing could lose us information -- for example, if some * concrete value is Top in the abstract domain L1 but not Top in L2, coalescing * means that x will be abstracted to the Top element in L1+L2.) * * [*]: Patrick Cousot & Radhia Cousot. Systematic design of program analysis * frameworks. POPL'79, pp 269—282. * https://cs.nyu.edu/~pcousot/publications.www/CousotCousot-POPL-79-ACM-p269--282-1979.pdf */ template <typename FirstDomain, typename... Domains> class DisjointUnionAbstractDomain final : public AbstractDomain< DisjointUnionAbstractDomain<FirstDomain, Domains...>> { public: DisjointUnionAbstractDomain() : m_variant(FirstDomain::top()) {} template <typename Domain> /* implicit */ DisjointUnionAbstractDomain(Domain d) : m_variant(d) {} ~DisjointUnionAbstractDomain() { static_assert( std::is_base_of<AbstractDomain<FirstDomain>, FirstDomain>::value, "All members of the disjoint union must inherit from AbstractDomain"); static_assert( all_true<(std::is_base_of<AbstractDomain<Domains>, Domains>::value)...>::value, "All members of the disjoint union must inherit from AbstractDomain"); } static DisjointUnionAbstractDomain top() { return DisjointUnionAbstractDomain(FirstDomain::top()); } static DisjointUnionAbstractDomain bottom() { return DisjointUnionAbstractDomain(FirstDomain::bottom()); } bool is_top() const override { return boost::apply_visitor([](const auto& dom) { return dom.is_top(); }, this->m_variant); } bool is_bottom() const override { return boost::apply_visitor([](const auto& dom) { return dom.is_bottom(); }, this->m_variant); } void set_to_top() override { boost::apply_visitor([](auto& dom) { dom.set_to_top(); }, this->m_variant); } void set_to_bottom() override { boost::apply_visitor([](auto& dom) { dom.set_to_bottom(); }, this->m_variant); } bool leq(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) const override; bool equals(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) const override; void join_with(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) override; void widen_with(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) override { this->join_with(other); } void meet_with(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) override; void narrow_with(const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) override { this->meet_with(other); } /* * This will throw if the domain contained in the union differs from the * requested Domain. */ template <typename Domain> const Domain get() const { if (is_top()) { return Domain::top(); } if (is_bottom()) { return Domain::bottom(); } return boost::get<Domain>(m_variant); } template <typename Domain> const boost::optional<Domain> maybe_get() const { if (is_top()) { return Domain::top(); } if (is_bottom()) { return Domain::bottom(); } auto* dom = boost::get<Domain>(&m_variant); if (dom == nullptr) { return boost::none; } return *dom; } template <typename Domain> void apply(std::function<void(Domain*)> operation) { auto* dom = boost::get<Domain>(&m_variant); if (dom) { operation(dom); } } /* * Return a numeric index representing the current type, if any. */ boost::optional<int> which() const { if (is_top() || is_bottom()) { return boost::none; } return m_variant.which(); } template <typename Visitor> static typename Visitor::result_type apply_visitor( const Visitor& visitor, const DisjointUnionAbstractDomain<FirstDomain, Domains...>& dom) { return boost::apply_visitor(visitor, dom.m_variant); } template <typename Visitor> static typename Visitor::result_type apply_visitor( const Visitor& visitor, const DisjointUnionAbstractDomain<FirstDomain, Domains...>& d1, const DisjointUnionAbstractDomain<FirstDomain, Domains...>& d2) { return boost::apply_visitor(visitor, d1.m_variant, d2.m_variant); } template <typename... Ts> friend std::ostream& ::operator<<(std::ostream&, const DisjointUnionAbstractDomain<Ts...>&); private: // Check if all template parameters are true (see // https://stackoverflow.com/questions/28253399/check-traits-for-all-variadic-template-arguments/28253503#28253503) template <bool...> struct bool_pack; template <bool... v> using all_true = std::is_same<bool_pack<true, v...>, bool_pack<v..., true>>; boost::variant<FirstDomain, Domains...> m_variant; }; } // namespace sparta template <typename... Domains> std::ostream& operator<<( std::ostream& o, const typename sparta::DisjointUnionAbstractDomain<Domains...>& du) { o << "[U] "; boost::apply_visitor([&o](const auto& dom) { o << dom; }, du.m_variant); return o; } namespace sparta { namespace duad_impl { /* * Top and Bottom are canonicalized via the leq and equals predicates, which * implement an equivalence relation on the extremal elements. Hence, even * though the actual type of the variant may vary, the different Top/Bottom * values of the underlying component domains are indistinguishable through the * AbstractDomain interface. */ class leq_visitor : public boost::static_visitor<bool> { public: template <typename Domain> bool operator()(const Domain& d1, const Domain& d2) const { return d1.leq(d2); } template <typename Domain, typename OtherDomain> bool operator()(const Domain& d1, const OtherDomain& d2) const { if (d1.is_bottom()) { return true; } if (d2.is_bottom()) { return false; } if (d2.is_top()) { return true; } if (d1.is_top()) { return false; } return false; } }; class equals_visitor : public boost::static_visitor<bool> { public: template <typename Domain> bool operator()(const Domain& d1, const Domain& d2) const { return d1.equals(d2); } template <typename Domain, typename OtherDomain> bool operator()(const Domain& d1, const OtherDomain& d2) const { if (d1.is_bottom()) { return d2.is_bottom(); } if (d1.is_top()) { return d2.is_top(); } return false; } }; class join_visitor : public boost::static_visitor<> { public: template <typename Domain> void operator()(Domain& d1, const Domain& d2) const { d1.join_with(d2); } template <typename Domain, typename OtherDomain> void operator()(Domain& d1, const OtherDomain&) const { d1.set_to_top(); } }; class meet_visitor : public boost::static_visitor<> { public: template <typename Domain> void operator()(Domain& d1, const Domain& d2) const { d1.meet_with(d2); } template <typename Domain, typename OtherDomain> void operator()(Domain& d1, const OtherDomain&) const { d1.set_to_bottom(); } }; } // namespace duad_impl template <typename FirstDomain, typename... Domains> void DisjointUnionAbstractDomain<FirstDomain, Domains...>::join_with( const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) { if (this->is_bottom()) { this->m_variant = other.m_variant; return; } if (other.is_bottom()) { return; } boost::apply_visitor(duad_impl::join_visitor(), this->m_variant, other.m_variant); } template <typename FirstDomain, typename... Domains> void DisjointUnionAbstractDomain<FirstDomain, Domains...>::meet_with( const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) { if (this->is_top()) { this->m_variant = other.m_variant; return; } if (other.is_top()) { return; } boost::apply_visitor(duad_impl::meet_visitor(), this->m_variant, other.m_variant); } template <typename FirstDomain, typename... Domains> bool DisjointUnionAbstractDomain<FirstDomain, Domains...>::leq( const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) const { return boost::apply_visitor(duad_impl::leq_visitor(), this->m_variant, other.m_variant); } template <typename FirstDomain, typename... Domains> bool DisjointUnionAbstractDomain<FirstDomain, Domains...>::equals( const DisjointUnionAbstractDomain<FirstDomain, Domains...>& other) const { return boost::apply_visitor(duad_impl::equals_visitor(), this->m_variant, other.m_variant); } } // namespace sparta