diff --git a/service/constant-propagation/ConstantArrayDomain.h b/service/constant-propagation/ConstantArrayDomain.h index 0a2b1d51bac..1dd32a95ca5 100644 --- a/service/constant-propagation/ConstantArrayDomain.h +++ b/service/constant-propagation/ConstantArrayDomain.h @@ -93,11 +93,7 @@ class ConstantArrayDomain final } // NOTE: This will throw if array_values() is Top. - const typename sparta::PatriciaTreeMapAbstractEnvironment::MapType& - bindings() const { - return array_values().bindings(); - } + const auto& bindings() const { return array_values().bindings(); } Domain get(uint32_t idx) const { if (this->is_top()) { diff --git a/sparta/include/sparta/AbstractEnvironment.h b/sparta/include/sparta/AbstractEnvironment.h new file mode 100644 index 00000000000..06c670e5a5a --- /dev/null +++ b/sparta/include/sparta/AbstractEnvironment.h @@ -0,0 +1,420 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include + +namespace sparta { + +namespace environment_impl { + +template +class MapValue; + +class value_is_bottom {}; + +} // namespace environment_impl + +/* + * An abstract environment is a type of abstract domain that maps the variables + * of a program to elements of a common abstract domain. For example, to perform + * range analysis one can use an abstract environment that maps variable names + * to intervals: + * + * {"x" -> [-1, 1], "i" -> [0, 10], ...} + * + * Another example is descriptive type analysis for Dex code, where one computes + * the set of all possible Java classes a register can hold a reference to at + * any point in the code: + * + * {"v0" -> {android.app.Fragment, java.lang.Object}, "v1" -> {...}, ...} + * + * This type of domain is commonly used for nonrelational (also called + * attribute-independent) analyses that do not track relationships among + * program variables. Please note that by definition of an abstract + * environment, if the value _|_ appears in a variable binding, then no valid + * execution state can ever be represented by this abstract environment. Hence, + * assigning _|_ to a variable is equivalent to setting the entire environment + * to _|_. + * + * In order to minimize the size of the underlying map, we do not explicitly + * represent bindings of a variable to the Top element. Hence, any variable that + * is not explicitly represented in the environment has a default value of Top. + * This representation is quite convenient in practice. It also allows us to + * manipulate large (or possibly infinite) variable sets with sparse assignments + * of non-Top values. + */ +template +class AbstractEnvironment final + : public AbstractDomainScaffolding, + AbstractEnvironment> { + public: + using Variable = typename Map::key_type; + using Domain = typename Map::mapped_type; + using MapType = Map; + + ~AbstractEnvironment() { + static_assert(std::is_base_of, Map>::value, + "Map doesn't inherit from AbstractMap"); + + using ValueInterface = typename Map::value_interface; + static_assert(std::is_base_of, + ValueInterface>::value, + "ValueInterface doesn't inherit from AbstractMapValue"); + static_assert(ValueInterface::default_value_kind == AbstractValueKind::Top, + "ValueInterface::default_value_kind is not Top"); + } + + /* + * The default constructor produces the Top value. + */ + AbstractEnvironment() + : AbstractDomainScaffolding, + AbstractEnvironment>() {} + + explicit AbstractEnvironment(AbstractValueKind kind) + : AbstractDomainScaffolding, + AbstractEnvironment>(kind) {} + + explicit AbstractEnvironment( + std::initializer_list> l) { + for (const auto& p : l) { + if (p.second.is_bottom()) { + this->set_to_bottom(); + return; + } + this->get_value()->insert_binding(p.first, p.second); + } + this->normalize(); + } + + bool is_value() const { return this->kind() == AbstractValueKind::Value; } + + size_t size() const { + RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, + invalid_abstract_value() + << expected_kind(AbstractValueKind::Value) + << actual_kind(this->kind())); + return this->get_value()->m_map.size(); + } + + const MapType& bindings() const { + RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, + invalid_abstract_value() + << expected_kind(AbstractValueKind::Value) + << actual_kind(this->kind())); + return this->get_value()->m_map; + } + + const Domain& get(const Variable& variable) const { + if (this->is_bottom()) { + static const Domain bottom = Domain::bottom(); + return bottom; + } + return this->get_value()->m_map.at(variable); + } + + AbstractEnvironment& set(const Variable& variable, const Domain& value) { + return set_internal(variable, value); + } + + AbstractEnvironment& set(const Variable& variable, Domain&& value) { + return set_internal(variable, std::move(value)); + } + + template // See `Map::update` + AbstractEnvironment& update(const Variable& variable, Operation&& operation) { + if (this->is_bottom()) { + return *this; + } + try { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + this->get_value()->m_map.update( + [operation = std::forward(operation)]( + const Domain& value) -> Domain { + Domain result = operation(value); + if (result.is_bottom()) { + throw environment_impl::value_is_bottom(); + } + return result; + }, + variable); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + this->get_value()->m_map.update( + [operation = + std::forward(operation)](Domain* value) -> void { + operation(value); + if (value->is_bottom()) { + throw environment_impl::value_is_bottom(); + } + }, + variable); + } + } catch (const environment_impl::value_is_bottom&) { + this->set_to_bottom(); + return *this; + } + this->normalize(); + return *this; + } + + using TransformResult = + typename std::conditional::type; + + template // See `Map::transform` + TransformResult transform(Operation&& f) { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + if (this->is_bottom()) { + return false; + } + bool res = this->get_value()->m_map.transform(std::forward(f)); + this->normalize(); + return res; + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + if (this->is_bottom()) { + return; + } + this->get_value()->m_map.transform(std::forward(f)); + this->normalize(); + return; + } + } + + bool erase_all_matching(const Variable& variable_mask) { + if (this->is_bottom()) { + return false; + } + bool res = this->get_value()->m_map.erase_all_matching(variable_mask); + this->normalize(); + return res; + } + + template // void(const std::pair&) + void visit(Visitor&& visitor) const { + if (this->is_bottom()) { + return; + } + this->get_value()->m_map.visit(std::forward(visitor)); + } + + static AbstractEnvironment bottom() { + return AbstractEnvironment(AbstractValueKind::Bottom); + } + + static AbstractEnvironment top() { + return AbstractEnvironment(AbstractValueKind::Top); + } + + private: + template + AbstractEnvironment& set_internal(const Variable& variable, D&& value) { + if (this->is_bottom()) { + return *this; + } + if (value.is_bottom()) { + this->set_to_bottom(); + return *this; + } + this->get_value()->insert_binding(variable, std::forward(value)); + this->normalize(); + return *this; + } +}; + +namespace environment_impl { + +/* + * The definition of an element of an abstract environment, i.e., a map from a + * (possibly infinite) set of variables to an abstract domain implemented as a + * hashtable. Variable bindings with the Top value are not stored in the + * hashtable. The map can never contain bindings with Bottom, as those are + * filtered out in AbstractEnvironment (the whole environment is set to + * Bottom in that case). The Meet and Narrowing operations abort and return + * AbstractValueKind::Bottom whenever a binding with Bottom is about to be + * created. + */ +template +class MapValue final : public AbstractValue> { + public: + using Variable = typename Map::key_type; + using Domain = typename Map::mapped_type; + + MapValue() = default; + + MapValue(const Variable& variable, Domain value) { + insert_binding(variable, std::move(value)); + } + + void clear() { m_map.clear(); } + + AbstractValueKind kind() const { + // If the map is empty, then all variables are implicitly bound to Top, + // i.e., the abstract environment itself is Top. + return (m_map.size() == 0) ? AbstractValueKind::Top + : AbstractValueKind::Value; + } + + bool leq(const MapValue& other) const { return m_map.leq(other.m_map); } + + bool equals(const MapValue& other) const { return m_map.equals(other.m_map); } + + AbstractValueKind join_with(const MapValue& other) { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + return join_like_operation( + other, + [](const Domain& x, const Domain& y) -> Domain { return x.join(y); }); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + return join_like_operation( + other, [](Domain* x, const Domain& y) -> void { x->join_with(y); }); + } + } + + AbstractValueKind widen_with(const MapValue& other) { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + return join_like_operation( + other, [](const Domain& x, const Domain& y) -> Domain { + return x.widening(y); + }); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + return join_like_operation( + other, [](Domain* x, const Domain& y) -> void { x->widen_with(y); }); + } + } + + AbstractValueKind meet_with(const MapValue& other) { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + return meet_like_operation( + other, + [](const Domain& x, const Domain& y) -> Domain { return x.meet(y); }); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + return meet_like_operation( + other, [](Domain* x, const Domain& y) -> void { x->meet_with(y); }); + } + } + + AbstractValueKind narrow_with(const MapValue& other) { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + return meet_like_operation( + other, [](const Domain& x, const Domain& y) -> Domain { + return x.narrowing(y); + }); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + return meet_like_operation( + other, [](Domain* x, const Domain& y) -> void { x->narrow_with(y); }); + } + } + + private: + template + void insert_binding(const Variable& variable, D&& value) { + // The Bottom value is handled in AbstractEnvironment and should + // never occur here. + RUNTIME_CHECK(!value.is_bottom(), internal_error()); + m_map.insert_or_assign(variable, std::forward(value)); + } + + template + AbstractValueKind join_like_operation(const MapValue& other, + Operation&& operation) { + m_map.intersection_with(std::forward(operation), other.m_map); + return kind(); + } + + template + AbstractValueKind meet_like_operation(const MapValue& other, + Operation&& operation) { + try { + if constexpr (Map::mutability == AbstractMapMutability::Immutable) { + m_map.union_with( + [operation = std::forward(operation)]( + const Domain& x, const Domain& y) -> Domain { + Domain result = operation(x, y); + if (result.is_bottom()) { + throw value_is_bottom(); + } + return result; + }, + other.m_map); + } else if constexpr (Map::mutability == AbstractMapMutability::Mutable) { + m_map.union_with( + [operation = std::forward(operation)]( + Domain* x, const Domain& y) -> void { + operation(x, y); + if (x->is_bottom()) { + throw value_is_bottom(); + } + }, + other.m_map); + } + } catch (const value_is_bottom&) { + clear(); + return AbstractValueKind::Bottom; + } + return kind(); + } + + private: + Map m_map; + + template + friend class sparta::AbstractEnvironment; +}; + +} // namespace environment_impl + +template +struct TopValueInterface final + : public AbstractMapValue> { + using type = Domain; + + static type default_value() { return type::top(); } + + static bool is_default_value(const type& x) { return x.is_top(); } + + static bool equals(const type& x, const type& y) { return x.equals(y); } + + static bool leq(const type& x, const type& y) { return x.leq(y); } + + constexpr static AbstractValueKind default_value_kind = + AbstractValueKind::Top; +}; + +} // namespace sparta + +template +inline std::ostream& operator<<( + std::ostream& o, const typename sparta::AbstractEnvironment& e) { + using namespace sparta; + switch (e.kind()) { + case AbstractValueKind::Bottom: { + o << "_|_"; + break; + } + case AbstractValueKind::Top: { + o << "T"; + break; + } + case AbstractValueKind::Value: { + o << "[#" << e.size() << "]"; + o << e.bindings(); + break; + } + } + return o; +} diff --git a/sparta/include/sparta/HashedAbstractEnvironment.h b/sparta/include/sparta/HashedAbstractEnvironment.h index f30bd38926e..040bd12ea26 100644 --- a/sparta/include/sparta/HashedAbstractEnvironment.h +++ b/sparta/include/sparta/HashedAbstractEnvironment.h @@ -7,30 +7,11 @@ #pragma once -#include -#include -#include -#include -#include -#include - -#include +#include #include namespace sparta { -namespace hae_impl { - -template -class MapValue; - -class value_is_bottom {}; - -} // namespace hae_impl - /* * An abstract environment is a type of abstract domain that maps the variables * of a program to elements of a common abstract domain. For example, to perform @@ -67,290 +48,11 @@ template , typename VariableEqual = std::equal_to> -class HashedAbstractEnvironment final - : public AbstractDomainScaffolding< - hae_impl::MapValue, - HashedAbstractEnvironment> { - public: - using Value = - hae_impl::MapValue; - - /* - * The default constructor produces the Top value. - */ - HashedAbstractEnvironment() - : AbstractDomainScaffolding() {} - - explicit HashedAbstractEnvironment(AbstractValueKind kind) - : AbstractDomainScaffolding(kind) {} - - explicit HashedAbstractEnvironment( - std::initializer_list> l) { - for (const auto& p : l) { - if (p.second.is_bottom()) { - this->set_to_bottom(); - return; - } - this->get_value()->insert_binding(p.first, p.second); - } - this->normalize(); - } - - bool is_value() const { return this->kind() == AbstractValueKind::Value; } - - size_t size() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map.size(); - } - - const std::unordered_map& - bindings() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map.bindings(); - } - - const Domain& get(const Variable& variable) const { - if (this->is_bottom()) { - static const Domain bottom = Domain::bottom(); - return bottom; - } - return this->get_value()->m_map.at(variable); - } - - HashedAbstractEnvironment& set(const Variable& variable, - const Domain& value) { - return set_internal(variable, value); - } - - HashedAbstractEnvironment& set(const Variable& variable, Domain&& value) { - return set_internal(variable, std::move(value)); - } - - template // void (Domain*) - HashedAbstractEnvironment& update(const Variable& variable, - Operation&& operation) { - if (this->is_bottom()) { - return *this; - } - try { - this->get_value()->m_map.update( - [operation = std::forward(operation)](Domain* value) { - operation(value); - if (value->is_bottom()) { - throw hae_impl::value_is_bottom(); - } - }, - variable); - } catch (const hae_impl::value_is_bottom&) { - this->set_to_bottom(); - return *this; - } - this->normalize(); - return *this; - } - - template // void(const std::pair&) - void visit(Visitor&& visitor) const { - if (this->is_bottom()) { - return; - } - this->get_value()->m_map.visit(std::forward(visitor)); - } - - static HashedAbstractEnvironment bottom() { - return HashedAbstractEnvironment(AbstractValueKind::Bottom); - } - - static HashedAbstractEnvironment top() { - return HashedAbstractEnvironment(AbstractValueKind::Top); - } - - private: - template - HashedAbstractEnvironment& set_internal(const Variable& variable, D&& value) { - if (this->is_bottom()) { - return *this; - } - if (value.is_bottom()) { - this->set_to_bottom(); - return *this; - } - this->get_value()->insert_binding(variable, std::forward(value)); - this->normalize(); - return *this; - } -}; - -} // namespace sparta - -template -inline std::ostream& operator<<( - std::ostream& o, - const typename sparta::HashedAbstractEnvironment& e) { - using namespace sparta; - switch (e.kind()) { - case AbstractValueKind::Bottom: { - o << "_|_"; - break; - } - case AbstractValueKind::Top: { - o << "T"; - break; - } - case AbstractValueKind::Value: { - o << "[#" << e.size() << "]"; - o << "{"; - auto& bindings = e.bindings(); - for (auto it = bindings.begin(); it != bindings.end();) { - o << it->first << " -> " << it->second; - ++it; - if (it != bindings.end()) { - o << ", "; - } - } - o << "}"; - break; - } - } - return o; -} - -namespace sparta { - -namespace hae_impl { - -/* - * The definition of an element of an abstract environment, i.e., a map from a - * (possibly infinite) set of variables to an abstract domain implemented as a - * hashtable. Variable bindings with the Top value are not stored in the - * hashtable. The hashtable can never contain bindings with Bottom, as those are - * filtered out in HashedAbstractEnvironment (the whole environment is set to - * Bottom in that case). The Meet and Narrowing operations abort and return - * AbstractValueKind::Bottom whenever a binding with Bottom is about to be - * created. - */ -template -class MapValue final - : public AbstractValue< - MapValue> { - public: - struct ValueInterface final : public AbstractMapValue { - using type = Domain; - - static type default_value() { return type::top(); } - - static bool is_default_value(const type& x) { return x.is_top(); } - - static bool equals(const type& x, const type& y) { return x.equals(y); } - - static bool leq(const type& x, const type& y) { return x.leq(y); } - - constexpr static AbstractValueKind default_value_kind = - AbstractValueKind::Top; - }; - - using MapType = - HashMap; - - MapValue() = default; - - MapValue(const Variable& variable, Domain value) { - insert_binding(variable, std::move(value)); - } - - void clear() { m_map.clear(); } - - AbstractValueKind kind() const { - // If the map is empty, then all variables are implicitly bound to Top, - // i.e., the abstract environment itself is Top. - return (m_map.size() == 0) ? AbstractValueKind::Top - : AbstractValueKind::Value; - } - - bool leq(const MapValue& other) const { return m_map.leq(other.m_map); } - - bool equals(const MapValue& other) const { return m_map.equals(other.m_map); } - - AbstractValueKind join_with(const MapValue& other) { - return join_like_operation( - other, [](Domain* x, const Domain& y) { x->join_with(y); }); - } - - AbstractValueKind widen_with(const MapValue& other) { - return join_like_operation( - other, [](Domain* x, const Domain& y) { x->widen_with(y); }); - } - - AbstractValueKind meet_with(const MapValue& other) { - return meet_like_operation( - other, [](Domain* x, const Domain& y) { x->meet_with(y); }); - } - - AbstractValueKind narrow_with(const MapValue& other) { - return meet_like_operation( - other, [](Domain* x, const Domain& y) { x->narrow_with(y); }); - } - - private: - template - void insert_binding(const Variable& variable, D&& value) { - // The Bottom value is handled in HashedAbstractEnvironment and should - // never occur here. - RUNTIME_CHECK(!value.is_bottom(), internal_error()); - m_map.insert_or_assign(variable, std::forward(value)); - } - - template // void(Domain*, const Domain&) - AbstractValueKind join_like_operation(const MapValue& other, - Operation&& operation) { - m_map.intersection_with(std::forward(operation), other.m_map); - return kind(); - } - - template // void(Domain*, const Domain&) - AbstractValueKind meet_like_operation(const MapValue& other, - Operation&& operation) { - try { - m_map.union_with( - [operation = std::forward(operation)]( - Domain* left, const Domain& right) { - operation(left, right); - if (left->is_bottom()) { - throw value_is_bottom(); - } - }, - other.m_map); - } catch (const value_is_bottom&) { - clear(); - return AbstractValueKind::Bottom; - } - return kind(); - } - - MapType m_map; - - template - friend class sparta::HashedAbstractEnvironment; -}; - -} // namespace hae_impl +using HashedAbstractEnvironment = + AbstractEnvironment, + VariableHash, + VariableEqual>>; } // namespace sparta diff --git a/sparta/include/sparta/PatriciaTreeHashMapAbstractEnvironment.h b/sparta/include/sparta/PatriciaTreeHashMapAbstractEnvironment.h index 44b5c8726a9..55a0531e4c3 100644 --- a/sparta/include/sparta/PatriciaTreeHashMapAbstractEnvironment.h +++ b/sparta/include/sparta/PatriciaTreeHashMapAbstractEnvironment.h @@ -7,321 +7,18 @@ #pragma once -#include -#include -#include -#include -#include -#include - -#include +#include #include namespace sparta { -namespace pthmae_impl { - -template -class MapValue; - -class value_is_bottom {}; - -} // namespace pthmae_impl - /* * An abstract environment based on `PatriciaTreeHashMap`. * - * See `PatriciaTreeMapAbstractEnvironment` for more information. - */ -template -class PatriciaTreeHashMapAbstractEnvironment final - : public AbstractDomainScaffolding< - pthmae_impl::MapValue, - PatriciaTreeHashMapAbstractEnvironment> { - public: - using Value = pthmae_impl::MapValue; - - using MapType = - PatriciaTreeHashMap; - - /* - * The default constructor produces the Top value. - */ - PatriciaTreeHashMapAbstractEnvironment() - : AbstractDomainScaffolding() {} - - explicit PatriciaTreeHashMapAbstractEnvironment(AbstractValueKind kind) - : AbstractDomainScaffolding( - kind) {} - - explicit PatriciaTreeHashMapAbstractEnvironment( - std::initializer_list> l) { - for (const auto& p : l) { - if (p.second.is_bottom()) { - this->set_to_bottom(); - return; - } - this->get_value()->insert_binding(p.first, p.second); - } - this->normalize(); - } - - size_t size() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map.size(); - } - - const MapType& bindings() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map; - } - - const Domain& get(const Variable& variable) const { - if (this->is_bottom()) { - static const Domain bottom = Domain::bottom(); - return bottom; - } - return this->get_value()->m_map.at(variable); - } - - PatriciaTreeHashMapAbstractEnvironment& set(const Variable& variable, - const Domain& value) { - return set_internal(variable, value); - } - - PatriciaTreeHashMapAbstractEnvironment& set(const Variable& variable, - Domain&& value) { - return set_internal(variable, std::move(value)); - } - - template // void(Domain*) - void transform(Operation&& f) { - if (this->is_bottom()) { - return; - } - this->get_value()->transform(std::forward(f)); - this->normalize(); - return; - } - - template // void(const std::pair&) - void visit(Visitor&& visitor) const { - if (this->is_bottom()) { - return; - } - this->get_value()->visit(std::forward(visitor)); - } - - PatriciaTreeHashMapAbstractEnvironment& clear() { - if (this->is_bottom()) { - return *this; - } - this->get_value()->clear(); - this->normalize(); - return *this; - } - - template // void(Domain*) - PatriciaTreeHashMapAbstractEnvironment& update(const Variable& variable, - Operation&& operation) { - if (this->is_bottom()) { - return *this; - } - try { - this->get_value()->m_map.update( - [operation = std::forward(operation)](Domain* x) -> void { - operation(x); - if (x->is_bottom()) { - throw pthmae_impl::value_is_bottom(); - } - }, - variable); - } catch (const pthmae_impl::value_is_bottom&) { - this->set_to_bottom(); - } - this->normalize(); - return *this; - } - - static PatriciaTreeHashMapAbstractEnvironment bottom() { - return PatriciaTreeHashMapAbstractEnvironment(AbstractValueKind::Bottom); - } - - static PatriciaTreeHashMapAbstractEnvironment top() { - return PatriciaTreeHashMapAbstractEnvironment(AbstractValueKind::Top); - } - - private: - template - PatriciaTreeHashMapAbstractEnvironment& set_internal(const Variable& variable, - D&& value) { - if (this->is_bottom()) { - return *this; - } - if (value.is_bottom()) { - this->set_to_bottom(); - return *this; - } - this->get_value()->insert_binding(variable, std::forward(value)); - this->normalize(); - return *this; - } -}; - -} // namespace sparta - -template -inline std::ostream& operator<<( - std::ostream& o, - const typename sparta::PatriciaTreeHashMapAbstractEnvironment& e) { - using namespace sparta; - switch (e.kind()) { - case AbstractValueKind::Bottom: { - o << "_|_"; - break; - } - case AbstractValueKind::Top: { - o << "T"; - break; - } - case AbstractValueKind::Value: { - o << "[#" << e.size() << "]"; - o << e.bindings(); - break; - } - } - return o; -} - -namespace sparta { - -namespace pthmae_impl { - -/* - * The definition of an element of an abstract environment, i.e., a map from a - * (possibly infinite) set of variables to an abstract domain implemented as a - * hashtable. Variable bindings with the Top value are not stored in the - * hashtable. The hashtable can never contain bindings with Bottom, as those are - * filtered out in PatriciaTreeHashMapAbstractEnvironment (the whole environment - * is set to Bottom in that case). The Meet and Narrowing operations abort and - * return AbstractValueKind::Bottom whenever a binding with Bottom is about to - * be created. + * See `AbstractEnvironment` for more information. */ template -class MapValue final : public AbstractValue> { - public: - struct ValueInterface final : public AbstractMapValue { - using type = Domain; - - static type default_value() { return type::top(); } - - static bool is_default_value(const type& x) { return x.is_top(); } - - static bool equals(const type& x, const type& y) { return x.equals(y); } - - static bool leq(const type& x, const type& y) { return x.leq(y); } - - constexpr static AbstractValueKind default_value_kind = - AbstractValueKind::Top; - }; - - MapValue() = default; - - MapValue(const Variable& variable, Domain value) { - insert_binding(variable, std::move(value)); - } - - void clear() { m_map.clear(); } - - AbstractValueKind kind() const { - // If the map is empty, then all variables are implicitly bound to Top, - // i.e., the abstract environment itself is Top. - return m_map.empty() ? AbstractValueKind::Top : AbstractValueKind::Value; - } - - bool leq(const MapValue& other) const { return m_map.leq(other.m_map); } - - bool equals(const MapValue& other) const { return m_map.equals(other.m_map); } - - AbstractValueKind join_with(const MapValue& other) { - return join_like_operation( - other, [](Domain* x, const Domain& y) { return x->join_with(y); }); - } - - AbstractValueKind widen_with(const MapValue& other) { - return join_like_operation( - other, [](Domain* x, const Domain& y) { return x->widen_with(y); }); - } - - AbstractValueKind meet_with(const MapValue& other) { - return meet_like_operation( - other, [](Domain* x, const Domain& y) { return x->meet_with(y); }); - } - - AbstractValueKind narrow_with(const MapValue& other) { - return meet_like_operation( - other, [](Domain* x, const Domain& y) { return x->narrow_with(y); }); - } - - private: - void insert_binding(const Variable& variable, Domain value) { - // The Bottom value is handled by the caller and should never occur here. - RUNTIME_CHECK(!value.is_bottom(), internal_error()); - m_map.insert_or_assign(variable, std::move(value)); - } - - template // void(Domain*) - void transform(Operation&& f) { - m_map.transform(std::forward(f)); - } - - template // void(const std::pair&) - void visit(Visitor&& visitor) const { - return m_map.visit(std::forward(visitor)); - } - - template // void(Domain*, const Domain&) - AbstractValueKind join_like_operation(const MapValue& other, - Operation&& operation) { - m_map.intersection_with(std::forward(operation), other.m_map); - return kind(); - } - - template // void(Domain*, const Domain&) - AbstractValueKind meet_like_operation(const MapValue& other, - Operation&& operation) { - try { - m_map.union_with( - [operation = std::forward(operation)](Domain* x, - const Domain& y) { - operation(x, y); - if (x->is_bottom()) { - throw value_is_bottom(); - } - }, - other.m_map); - return kind(); - } catch (const value_is_bottom&) { - clear(); - return AbstractValueKind::Bottom; - } - } - - PatriciaTreeHashMap m_map; - - template - friend class sparta::PatriciaTreeHashMapAbstractEnvironment; -}; - -} // namespace pthmae_impl +using PatriciaTreeHashMapAbstractEnvironment = AbstractEnvironment< + PatriciaTreeHashMap>>; } // namespace sparta diff --git a/sparta/include/sparta/PatriciaTreeMapAbstractEnvironment.h b/sparta/include/sparta/PatriciaTreeMapAbstractEnvironment.h index ac65e8611ac..648398f6104 100644 --- a/sparta/include/sparta/PatriciaTreeMapAbstractEnvironment.h +++ b/sparta/include/sparta/PatriciaTreeMapAbstractEnvironment.h @@ -7,339 +7,22 @@ #pragma once -#include -#include -#include -#include -#include -#include - -#include +#include #include namespace sparta { -namespace ptmae_impl { - -template -class MapValue; - -class value_is_bottom {}; - -} // namespace ptmae_impl - /* * An abstract environment based on Patricia trees that is cheap to copy. * * In order to minimize the size of the underlying tree, we do not explicitly * represent bindings of a variable to the Top element. * - * See HashedAbstractEnvironment.h for more details about abstract + * See AbstractEnvironment.h for more details about abstract * environments. */ template -class PatriciaTreeMapAbstractEnvironment final - : public AbstractDomainScaffolding< - ptmae_impl::MapValue, - PatriciaTreeMapAbstractEnvironment> { - public: - using Value = ptmae_impl::MapValue; - - using MapType = - PatriciaTreeMap; - - /* - * The default constructor produces the Top value. - */ - PatriciaTreeMapAbstractEnvironment() - : AbstractDomainScaffolding() { - } - - explicit PatriciaTreeMapAbstractEnvironment(AbstractValueKind kind) - : AbstractDomainScaffolding( - kind) {} - - explicit PatriciaTreeMapAbstractEnvironment( - std::initializer_list> l) { - for (const auto& p : l) { - if (p.second.is_bottom()) { - this->set_to_bottom(); - return; - } - this->get_value()->insert_binding(p.first, p.second); - } - this->normalize(); - } - - size_t size() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map.size(); - } - - const MapType& bindings() const { - RUNTIME_CHECK(this->kind() == AbstractValueKind::Value, - invalid_abstract_value() - << expected_kind(AbstractValueKind::Value) - << actual_kind(this->kind())); - return this->get_value()->m_map; - } - - const Domain& get(const Variable& variable) const { - if (this->is_bottom()) { - static const Domain bottom = Domain::bottom(); - return bottom; - } - return this->get_value()->m_map.at(variable); - } - - PatriciaTreeMapAbstractEnvironment& set(const Variable& variable, - const Domain& value) { - return set_internal(variable, value); - } - - PatriciaTreeMapAbstractEnvironment& set(const Variable& variable, - Domain&& value) { - return set_internal(variable, std::move(value)); - } - - template // Domain(const Domain&) - bool transform(Operation&& f) { - if (this->is_bottom()) { - return false; - } - bool res = this->get_value()->transform(std::forward(f)); - this->normalize(); - return res; - } - - template // void(const std::pair&) - void visit(Visitor&& visitor) const { - if (this->is_bottom()) { - return; - } - this->get_value()->visit(std::forward(visitor)); - } - - bool erase_all_matching(const Variable& variable_mask) { - if (this->is_bottom()) { - return false; - } - bool res = this->get_value()->erase_all_matching(variable_mask); - this->normalize(); - return res; - } - - PatriciaTreeMapAbstractEnvironment& clear() { - if (this->is_bottom()) { - return *this; - } - this->get_value()->clear(); - this->normalize(); - return *this; - } - - template // Domain(const Domain&) - PatriciaTreeMapAbstractEnvironment& update(const Variable& variable, - Operation&& operation) { - if (this->is_bottom()) { - return *this; - } - try { - this->get_value()->m_map.update( - [operation = std::forward(operation)](const Domain& x) { - Domain result = operation(x); - if (result.is_bottom()) { - throw ptmae_impl::value_is_bottom(); - } - return result; - }, - variable); - } catch (const ptmae_impl::value_is_bottom&) { - this->set_to_bottom(); - } - this->normalize(); - return *this; - } - - static PatriciaTreeMapAbstractEnvironment bottom() { - return PatriciaTreeMapAbstractEnvironment(AbstractValueKind::Bottom); - } - - static PatriciaTreeMapAbstractEnvironment top() { - return PatriciaTreeMapAbstractEnvironment(AbstractValueKind::Top); - } - - private: - template - PatriciaTreeMapAbstractEnvironment& set_internal(const Variable& variable, - D&& value) { - if (this->is_bottom()) { - return *this; - } - if (value.is_bottom()) { - this->set_to_bottom(); - return *this; - } - this->get_value()->insert_binding(variable, std::forward(value)); - this->normalize(); - return *this; - } -}; - -} // namespace sparta - -template -inline std::ostream& operator<<( - std::ostream& o, - const typename sparta::PatriciaTreeMapAbstractEnvironment& - e) { - using namespace sparta; - switch (e.kind()) { - case AbstractValueKind::Bottom: { - o << "_|_"; - break; - } - case AbstractValueKind::Top: { - o << "T"; - break; - } - case AbstractValueKind::Value: { - o << "[#" << e.size() << "]"; - o << e.bindings(); - break; - } - } - return o; -} - -namespace sparta { - -namespace ptmae_impl { - -/* - * The definition of an element of an abstract environment, i.e., a map from a - * (possibly infinite) set of variables to an abstract domain implemented as a - * hashtable. Variable bindings with the Top value are not stored in the - * hashtable. The hashtable can never contain bindings with Bottom, as those are - * filtered out in PatriciaTreeMapAbstractEnvironment (the whole environment is - * set to Bottom in that case). The Meet and Narrowing operations abort and - * return AbstractValueKind::Bottom whenever a binding with Bottom is about to - * be created. - */ -template -class MapValue final : public AbstractValue> { - public: - struct ValueInterface final : public AbstractMapValue { - using type = Domain; - - static type default_value() { return type::top(); } - - static bool is_default_value(const type& x) { return x.is_top(); } - - static bool equals(const type& x, const type& y) { return x.equals(y); } - - static bool leq(const type& x, const type& y) { return x.leq(y); } - - constexpr static AbstractValueKind default_value_kind = - AbstractValueKind::Top; - }; - - MapValue() = default; - - MapValue(const Variable& variable, Domain value) { - insert_binding(variable, std::move(value)); - } - - void clear() { m_map.clear(); } - - AbstractValueKind kind() const { - // If the map is empty, then all variables are implicitly bound to Top, - // i.e., the abstract environment itself is Top. - return m_map.empty() ? AbstractValueKind::Top : AbstractValueKind::Value; - } - - bool leq(const MapValue& other) const { return m_map.leq(other.m_map); } - - bool equals(const MapValue& other) const { return m_map.equals(other.m_map); } - - AbstractValueKind join_with(const MapValue& other) { - return join_like_operation( - other, [](const Domain& x, const Domain& y) { return x.join(y); }); - } - - AbstractValueKind widen_with(const MapValue& other) { - return join_like_operation( - other, [](const Domain& x, const Domain& y) { return x.widening(y); }); - } - - AbstractValueKind meet_with(const MapValue& other) { - return meet_like_operation( - other, [](const Domain& x, const Domain& y) { return x.meet(y); }); - } - - AbstractValueKind narrow_with(const MapValue& other) { - return meet_like_operation( - other, [](const Domain& x, const Domain& y) { return x.narrowing(y); }); - } - - private: - void insert_binding(const Variable& variable, Domain value) { - // The Bottom value is handled by the caller and should never occur here. - RUNTIME_CHECK(!value.is_bottom(), internal_error()); - m_map.insert_or_assign(variable, std::move(value)); - } - - template // Domain(const Domain&) - bool transform(Operation&& f) { - return m_map.transform(std::forward(f)); - } - - template // void(const std::pair&) - void visit(Visitor&& visitor) const { - return m_map.visit(std::forward(visitor)); - } - - bool erase_all_matching(const Variable& variable_mask) { - return m_map.erase_all_matching(variable_mask); - } - - template // Domain(const Domain&, const Domain&) - AbstractValueKind join_like_operation(const MapValue& other, - Operation&& operation) { - m_map.intersection_with(std::forward(operation), other.m_map); - return kind(); - } - - template // Domain(const Domain&, const Domain&) - AbstractValueKind meet_like_operation(const MapValue& other, - Operation&& operation) { - try { - m_map.union_with( - [operation = std::forward(operation)](const Domain& x, - const Domain& y) { - Domain result = operation(x, y); - if (result.is_bottom()) { - throw value_is_bottom(); - } - return result; - }, - other.m_map); - return kind(); - } catch (const value_is_bottom&) { - clear(); - return AbstractValueKind::Bottom; - } - } - - PatriciaTreeMap m_map; - - template - friend class sparta::PatriciaTreeMapAbstractEnvironment; -}; - -} // namespace ptmae_impl +using PatriciaTreeMapAbstractEnvironment = AbstractEnvironment< + PatriciaTreeMap>>; } // namespace sparta diff --git a/sparta/test/MonotonicFixpointIteratorTest.cpp b/sparta/test/MonotonicFixpointIteratorTest.cpp index 181c390fd07..62a49235204 100644 --- a/sparta/test/MonotonicFixpointIteratorTest.cpp +++ b/sparta/test/MonotonicFixpointIteratorTest.cpp @@ -703,19 +703,19 @@ class IntegerSetAbstractDomain final bool m_top; }; -using AbstractEnvironment = +using AbstractEnvironmentT = PatriciaTreeMapAbstractEnvironment; template