include/DirectProductAbstractDomain.h (178 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 <algorithm> #include <cstddef> #include <functional> #include <initializer_list> #include <sstream> #include <tuple> #include <type_traits> #include "AbstractDomain.h" // Forward declarations. namespace sparta { template <typename DerivedAgain, typename... DomainsAgain> class ReducedProductAbstractDomain; } // namespace sparta namespace sparta { /* * Direct product of abstract domains D1 x ... x Dn consists of tuples of * abstract values (v1, ..., vn). Note that the difference between this and the * subclass ReducedProductAbstractDomain is the way we handle components that * are bottom. DirectProductAbstractDomain doesn't do normalization, * meaning that a non-Bottom direct product can contain components that * are not Bottom. * * By default the entire product becomes bottom only if all the components are * bottom. Similarly, setting the product to top marks every component as top. * This class has the component-wise product logic. The subclass * ReducedProductAbstractDomain has the additional normalization logic. */ template <typename Derived, typename... Domains> class DirectProductAbstractDomain : public AbstractDomain<Derived> { public: ~DirectProductAbstractDomain() { // The destructor is the only method that is guaranteed to be created when a // class template is instantiated. This is a good place to perform all the // sanity checks on the template parameters. static_assert( sizeof...(Domains) >= 2, "DirectProductAbstractDomain requires at least two parameters"); } /* * Defining a public variadic constructor will invariably lead to instances of * the "Most Vexing Parse". Passing a tuple of elements as a single argument * to the constructor circumvents the issue without sacrificing readability. */ explicit DirectProductAbstractDomain(std::tuple<Domains...> product) : m_product(std::move(product)) {} /* * A default constructor is required. */ DirectProductAbstractDomain() : DirectProductAbstractDomain(std::tuple<Domains...>{}) {} /* * Returns a read-only reference to a component in the tuple. */ template <size_t Index> const typename std::tuple_element<Index, std::tuple<Domains...>>::type& get() const { return std::get<Index>(m_product); } /* * Updates a component of the tuple by applying a user-defined operation. */ template <size_t Index> void apply( std::function<void( typename std::tuple_element<Index, std::tuple<Domains...>>::type*)> operation, bool do_reduction = false) { if (is_bottom()) { return; } operation(&std::get<Index>(m_product)); } virtual bool is_bottom() const override { return all_of([](auto&& component) { return component.is_bottom(); }); } bool is_top() const override { return all_of([](auto&& component) { return component.is_top(); }); } bool leq(const Derived& other_domain) const override { return compare_with(other_domain, [](auto&& self, auto&& other) { return self.leq(other); }); } bool equals(const Derived& other_domain) const override { return compare_with(other_domain, [](auto&& self, auto&& other) { return self.equals(other); }); } void set_to_bottom() override { return tuple_apply( [](auto&&... c) { discard({(c.set_to_bottom(), 0)...}); }, m_product); } void set_to_top() override { return tuple_apply([](auto&&... c) { discard({(c.set_to_top(), 0)...}); }, m_product); } // We leave the Meet and Narrowing methods virtual, because one might want // to refine the result of these operations. virtual void meet_with(const Derived& other_domain) override { combine_with( other_domain, [](auto&& self, auto&& other) { self.meet_with(other); }, /* smash_bottom */ false); } virtual void narrow_with(const Derived& other_domain) override { combine_with( other_domain, [](auto&& self, auto&& other) { self.narrow_with(other); }, /* smash_bottom */ false); } // reduce() should only refine (lower) a given component of a product based on // the information in the other components. As such, it only makes sense to // call reduce() after meet/narrow -- operations which can refine the // components of a product. However, we may still need to canonicalize our // product after a join/widen, so these methods are virtual as well. virtual void join_with(const Derived& other_domain) override { combine_with( other_domain, [](auto&& self, auto&& other) { self.join_with(other); }, /* smash_bottom */ false); } virtual void widen_with(const Derived& other_domain) override { combine_with( other_domain, [](auto&& self, auto&& other) { self.widen_with(other); }, /* smash_bottom */ false); } friend std::ostream& operator<<(std::ostream& o, const Derived& p) { o << "("; tuple_print(o, p.m_product); o << ")"; return o; } private: // When using tuple_apply, we often need to expand a parameter pack in order // to perform an operation on each parameter. This is achieved using the // expansion of a brace-enclosed initializer {(expr)...}, where expr operates // via side effects. Since we don't use the result of the initializer // expansion, some compilers may complain. We use this function to explicitly // discard the initializer list and silence those compilers. template <typename T> static void discard(const std::initializer_list<T>&) {} // The following methods are used to unpack a tuple of operations/predicates // and apply them to a tuple of abstract values. We use an implementation of // C++ 17's std::apply to iterate over the elements of a tuple (see // http://en.cppreference.com/w/cpp/utility/apply). template <class F, class Tuple, std::size_t... I> constexpr decltype(auto) static tuple_apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) { return f(std::get<I>(std::forward<Tuple>(t))...); } template <class F, class Tuple> constexpr decltype(auto) static tuple_apply(F&& f, Tuple&& t) { return tuple_apply_impl( std::forward<F>(f), std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>{}); } template <class Predicate> bool all_of(Predicate&& predicate) const { return tuple_apply( [predicate](const Domains&... component) { bool result = true; discard({(result &= predicate(component))...}); return result; }, m_product); } template <class Predicate> bool any_of(Predicate&& predicate) const { return tuple_apply( [predicate](const Domains&... component) { bool result = false; discard({(result |= predicate(component))...}); return result; }, m_product); } // We also use the template deduction mechanism combined with recursion to // simulate a sequential iteration over the components ranging from index 0 to // sizeof...(Domains) - 1. template <size_t Index = 0, class Predicate> std::enable_if_t<Index == sizeof...(Domains), bool> compare_with( const Derived& other, Predicate&& predicate) const { return true; } template <size_t Index = 0, class Predicate> std::enable_if_t<Index != sizeof...(Domains), bool> compare_with( const Derived& other, Predicate&& predicate) const { if (!predicate(std::get<Index>(m_product), std::get<Index>(other.m_product))) { return false; } return compare_with<Index + 1>(other, predicate); } template <size_t Index = 0, class Operation> std::enable_if_t<Index == sizeof...(Domains)> combine_with( const Derived& other, Operation&& operation, bool smash_bottom) {} template <size_t Index = 0, class Operation> std::enable_if_t<Index != sizeof...(Domains)> combine_with( const Derived& other, Operation&& operation, bool smash_bottom) { auto& component = std::get<Index>(m_product); operation(component, std::get<Index>(other.m_product)); if (smash_bottom && component.is_bottom()) { set_to_bottom(); return; } combine_with<Index + 1>(other, operation, smash_bottom); } template <class Tuple, std::size_t... I> static void tuple_print_impl(std::ostream& o, Tuple&& t, std::index_sequence<I...>) { discard({0, (void(o << (I == 0 ? "" : ", ") << std::get<I>(t)), 0)...}); } template <class Tuple> static void tuple_print(std::ostream& o, Tuple&& t) { return tuple_print_impl( o, std::forward<Tuple>(t), std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>{}); } std::tuple<Domains...> m_product; template <typename DerivedAgain, typename... DomainsAgain> friend class ReducedProductAbstractDomain; }; } // namespace sparta