include/LiftedDomain.h (113 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 <memory>
#include <ostream>
#include <type_traits>
#include "AbstractDomain.h"
#include "Exceptions.h"
namespace sparta {
/*
* Augments an underlying domain -- D -- with a new least element. In
* documentation and output formats, the underlying domain's existing least
* element will be referred to by the symbol * and the new least element as _|_.
*
* See Page 39 of Moeller and Schwartzbach [0] for potential uses of this
* combinator in abstract interpretation.
*
* [0] Anders Moeller and Michael I. Schwatzbach. Static Program Analysis.
* (December 2019). Retrieved April 23, 2020 from
* https://cs.au.dk/~amoeller/spa/spa.pdf
*/
template <typename D>
class LiftedDomain final : public AbstractDomain<LiftedDomain<D>> {
static_assert(std::is_convertible<D*, AbstractDomain<D>*>::value,
"LiftedDomain must wrap another domain.");
public:
static LiftedDomain bottom() { return LiftedDomain{nullptr}; }
static LiftedDomain lifted(D underlying) {
return LiftedDomain{std::make_unique<D>(std::move(underlying))};
}
static LiftedDomain top() {
return LiftedDomain{std::make_unique<D>(D::top())};
}
/* Default constructor produces the default value in underlying domain. */
LiftedDomain() : LiftedDomain(std::make_unique<D>()) {}
LiftedDomain(LiftedDomain&&) = default;
LiftedDomain& operator=(LiftedDomain&&) = default;
LiftedDomain(const LiftedDomain& that) {
if (that.m_underlying) {
m_underlying = std::make_unique<D>(*that.m_underlying);
}
}
LiftedDomain& operator=(const LiftedDomain& that) {
m_underlying = that.m_underlying ? std::make_unique<D>(*that.m_underlying)
: std::unique_ptr<D>();
return *this;
}
bool is_bottom() const override { return !m_underlying; }
bool is_top() const override {
return m_underlying && m_underlying->is_top();
}
bool is_lifted() const { return !is_bottom(); }
D& lowered() {
RUNTIME_CHECK(is_lifted(), undefined_operation());
return *m_underlying;
}
const D& lowered() const {
RUNTIME_CHECK(is_lifted(), undefined_operation());
return *m_underlying;
}
bool leq(const LiftedDomain& that) const override {
if (is_bottom()) {
return true;
} else if (that.is_bottom()) {
return false;
} else {
return m_underlying->leq(*that.m_underlying);
}
}
bool equals(const LiftedDomain& that) const override {
return (is_bottom() && that.is_bottom()) ||
m_underlying->equals(*that.m_underlying);
}
void set_to_bottom() override { m_underlying = nullptr; }
void set_to_top() override { m_underlying = std::make_unique<D>(D::top()); }
/*
* _|_ \/ x = x
* x \/ _|_ = x
* x \/ y = x \/' y
*
* Where \/' is the join on the underlying domain.
*/
void join_with(const LiftedDomain& that) override {
if (is_bottom()) {
*this = that;
} else if (!that.is_bottom()) {
m_underlying->join_with(*that.m_underlying);
}
}
/*
* _|_ W x = x
* x W _|_ = x
* x W y = x W' y
*
* Where W' is the widening on the underlying domain.
*/
void widen_with(const LiftedDomain& that) override {
if (is_bottom()) {
*this = that;
} else if (!that.is_bottom()) {
m_underlying->widen_with(*that.m_underlying);
}
}
/*
* _|_ /\ _ = _|_
* _ /\ _|_ = _|_
* x /\ y = x /\' y
*
* Where /\' is the meet on the underlying domain.
*/
void meet_with(const LiftedDomain& that) override {
if (is_bottom()) {
return;
} else if (that.is_bottom()) {
set_to_bottom();
} else {
m_underlying->meet_with(*that.m_underlying);
}
}
/*
* _|_ N x = _|_
* x N _|_ = _|_
* x N y = x N' y
*
* Where N' is the narrowing on the underlying domain.
*/
void narrow_with(const LiftedDomain& that) override {
if (is_bottom()) {
return;
} else if (that.is_bottom()) {
set_to_bottom();
} else {
m_underlying->narrow_with(*that.m_underlying);
}
}
private:
// Bottom for the lifted domain is represented by a null underlying pointer.
std::unique_ptr<D> m_underlying = nullptr;
explicit LiftedDomain(std::unique_ptr<D> underlying)
: m_underlying(std::move(underlying)) {}
};
} // namespace sparta
template <typename D>
inline std::ostream& operator<<(std::ostream& o,
const sparta::LiftedDomain<D>& i) {
if (i.is_bottom()) {
return o << "_|_";
}
if (i.is_top()) {
return o << "T";
}
const auto& under = i.lowered();
if (under.is_bottom()) {
return o << "*";
}
return o << under;
}