mirror of
https://github.com/Z3Prover/z3
synced 2025-04-06 17:44:08 +00:00
It is not unlike other fuzz bugs: it exercises some behavior that applications are unlikely to expose. In this case, a rule body expanded into a conjunction with more than 1M formulas (with a lot of repetition). The original rule representation assumed silently that the number of constraints in a body would fit within 20 bits, but reality allowed bodies with as many as 2^{32} - 1 constraints. So "minimizing" the bug as @agurfinkel asks for seems not to make too much sense. Just running the samples in debug mode points to the root cause. Since fuzz bugs are not from applications and fuzz tools have the potential for creating a large number of issues, I find it reasonable to push some basic pro-active asks on filers: - reproduce bug in debug builds to assess whether a debug assert triggers. - minimize or keep it simpler when possible (in this case it does not apply) - perform basic diagnostics/triage. I am basically asking to push this part of the work on to the fuzzer. Otherwise, addressing random bugs doesn't scale. Triaging should have pointed to the root cause. Now, there tends to be something to learn from bugs. In this case, the question was: "can we avoid constraints with duplications"? In particular, it points to a basic inefficiency of extracting conjunctions (and disjunctions). The function didn't deduplicate. So I added deduplication into this function. It is used throughout z3 code base so could expose latent issues. We will see.
391 lines
12 KiB
C++
391 lines
12 KiB
C++
/*++
|
|
Copyright (c) 2006 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dl_rule.h
|
|
|
|
Abstract:
|
|
|
|
<abstract>
|
|
|
|
Author:
|
|
|
|
Leonardo de Moura (leonardo) 2010-05-17.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#pragma once
|
|
|
|
#include "ast/ast.h"
|
|
#include "muz/base/dl_costs.h"
|
|
#include "muz/base/dl_util.h"
|
|
#include "ast/used_vars.h"
|
|
#include "tactic/proof_converter.h"
|
|
#include "tactic/model_converter.h"
|
|
#include "ast/rewriter/ast_counter.h"
|
|
#include "ast/rewriter/rewriter.h"
|
|
#include "muz/base/hnf.h"
|
|
#include "qe/qe_lite.h"
|
|
#include "ast/rewriter/var_subst.h"
|
|
#include "ast/datatype_decl_plugin.h"
|
|
#include "ast/rewriter/label_rewriter.h"
|
|
|
|
namespace datalog {
|
|
|
|
class rule;
|
|
class rule_manager;
|
|
class rule_set;
|
|
class table;
|
|
class context;
|
|
|
|
typedef obj_ref<rule, rule_manager> rule_ref;
|
|
typedef ref_vector<rule, rule_manager> rule_ref_vector;
|
|
typedef ptr_vector<rule> rule_vector;
|
|
|
|
|
|
struct uninterpreted_function_finder_proc {
|
|
ast_manager& m;
|
|
datatype_util m_dt;
|
|
dl_decl_util m_dl;
|
|
bool m_found;
|
|
func_decl* m_func;
|
|
uninterpreted_function_finder_proc(ast_manager& m):
|
|
m(m), m_dt(m), m_dl(m), m_found(false), m_func(nullptr) {}
|
|
void operator()(var * n) { }
|
|
void operator()(quantifier * n) { }
|
|
void operator()(app * n) {
|
|
if (is_uninterp(n) && !m_dl.is_rule_sort(n->get_decl()->get_range())) {
|
|
m_found = true;
|
|
m_func = n->get_decl();
|
|
}
|
|
else if (m_dt.is_accessor(n)) {
|
|
sort* s = n->get_arg(0)->get_sort();
|
|
SASSERT(m_dt.is_datatype(s));
|
|
if (m_dt.get_datatype_constructors(s)->size() > 1) {
|
|
m_found = true;
|
|
m_func = n->get_decl();
|
|
}
|
|
}
|
|
}
|
|
void reset() { m_found = false; m_func = nullptr; }
|
|
|
|
bool found(func_decl*& f) const { f = m_func; return m_found; }
|
|
};
|
|
|
|
struct quantifier_finder_proc {
|
|
bool m_exist;
|
|
bool m_univ;
|
|
bool m_lambda;
|
|
quantifier_finder_proc() : m_exist(false), m_univ(false), m_lambda(false) {}
|
|
void operator()(var * n) { }
|
|
void operator()(quantifier * n) {
|
|
switch (n->get_kind()) {
|
|
case forall_k: m_univ = true; break;
|
|
case exists_k: m_exist = true; break;
|
|
case lambda_k: m_lambda = true; break;
|
|
}
|
|
}
|
|
void operator()(app * n) { }
|
|
void reset() { m_exist = m_univ = m_lambda = false; }
|
|
};
|
|
|
|
struct fd_finder_proc {
|
|
ast_manager& m;
|
|
bv_util m_bv;
|
|
bool m_is_fd;
|
|
fd_finder_proc(ast_manager& m): m(m), m_bv(m), m_is_fd(true) {}
|
|
|
|
bool is_fd() const { return m_is_fd; }
|
|
bool is_fd(sort* s) { return m.is_bool(s) || m_bv.is_bv_sort(s); }
|
|
void operator()(var* n) { m_is_fd &= is_fd(n->get_sort()); }
|
|
void operator()(quantifier* ) { m_is_fd = false; }
|
|
void operator()(app* n) { m_is_fd &= is_fd(n->get_decl()->get_range()); }
|
|
void reset() { m_is_fd = true; }
|
|
};
|
|
|
|
|
|
/**
|
|
\brief Manager for the \c rule class
|
|
|
|
\remark \c rule_manager objects are interchangeable as long as they
|
|
contain the same \c ast_manager object.
|
|
*/
|
|
class rule_manager
|
|
{
|
|
ast_manager& m;
|
|
context& m_ctx;
|
|
rule_counter m_counter;
|
|
used_vars m_used;
|
|
var_idx_set m_var_idx;
|
|
expr_free_vars m_free_vars;
|
|
app_ref_vector m_body;
|
|
app_ref m_head;
|
|
expr_ref_vector m_args;
|
|
bool_vector m_neg;
|
|
hnf m_hnf;
|
|
qe_lite m_qe;
|
|
label_rewriter m_rwr;
|
|
mutable uninterpreted_function_finder_proc m_ufproc;
|
|
mutable quantifier_finder_proc m_qproc;
|
|
mutable expr_sparse_mark m_visited;
|
|
mutable fd_finder_proc m_fd_proc;
|
|
|
|
|
|
// only the context can create a rule_manager
|
|
friend class context;
|
|
|
|
explicit rule_manager(context& ctx);
|
|
|
|
/**
|
|
\brief Move functions from predicate tails into the interpreted tail by introducing new variables.
|
|
*/
|
|
void hoist_compound_predicates(unsigned num_bound, app_ref& head, app_ref_vector& body);
|
|
|
|
void hoist_compound(unsigned& num_bound, app_ref& fml, app_ref_vector& body);
|
|
|
|
void flatten_body(app_ref_vector& body);
|
|
|
|
void remove_labels(expr_ref& fml, proof_ref& pr);
|
|
|
|
void check_app(expr* e);
|
|
|
|
bool contains_predicate(expr* fml) const;
|
|
|
|
void bind_variables(expr* fml, bool is_forall, expr_ref& result);
|
|
|
|
void mk_negations(app_ref_vector& body, bool_vector& is_negated);
|
|
|
|
void mk_rule_core(expr* fml, proof* p, rule_set& rules, symbol const& name);
|
|
|
|
void mk_horn_rule(expr* fml, proof* p, rule_set& rules, symbol const& name);
|
|
|
|
static expr_ref mk_implies(app_ref_vector const& body, expr* head);
|
|
|
|
unsigned extract_horn(expr* fml, app_ref_vector& body, app_ref& head);
|
|
|
|
/**
|
|
\brief Perform cheap quantifier elimination to reduce the number of variables in the interpreted tail.
|
|
*/
|
|
void reduce_unbound_vars(rule_ref& r);
|
|
|
|
void reset_collect_vars();
|
|
|
|
var_idx_set& finalize_collect_vars();
|
|
|
|
public:
|
|
|
|
ast_manager& get_manager() const { return m; }
|
|
|
|
void inc_ref(rule * r);
|
|
|
|
void dec_ref(rule * r);
|
|
|
|
used_vars& reset_used() { m_used.reset(); return m_used; }
|
|
|
|
var_idx_set& collect_vars(expr * pred);
|
|
|
|
var_idx_set& collect_vars(expr * e1, expr* e2);
|
|
|
|
var_idx_set& collect_rule_vars(rule * r);
|
|
|
|
var_idx_set& collect_rule_vars_ex(rule * r, app* t);
|
|
|
|
var_idx_set& collect_tail_vars(rule * r);
|
|
|
|
void accumulate_vars(expr* pred);
|
|
|
|
// ptr_vector<sort>& get_var_sorts() { return m_vars; }
|
|
|
|
var_idx_set& get_var_idx() { return m_var_idx; }
|
|
|
|
/**
|
|
\brief Create a Datalog rule from a Horn formula.
|
|
The formula is of the form (forall (...) (forall (...) (=> (and ...) head)))
|
|
|
|
*/
|
|
void mk_rule(expr* fml, proof* p, rule_set& rules, symbol const& name = symbol::null);
|
|
|
|
/**
|
|
\brief Create a Datalog query from an expression.
|
|
The formula is of the form (exists (...) (exists (...) (and ...))
|
|
*/
|
|
func_decl* mk_query(expr* query, rule_set& rules);
|
|
|
|
/**
|
|
\brief Create a Datalog rule head :- tail[0], ..., tail[n-1].
|
|
Return 0 if it is not a valid rule.
|
|
|
|
\remark A tail may contain negation. tail[i] is assumed to be negated if is_neg != 0 && is_neg[i] == true
|
|
*/
|
|
rule * mk(app * head, unsigned n, app * const * tail, bool const * is_neg = nullptr,
|
|
symbol const& name = symbol::null, bool normalize = true);
|
|
|
|
/**
|
|
\brief Create a rule with the same tail as \c source and with a specified head.
|
|
*/
|
|
rule * mk(rule const * source, app * new_head, symbol const& name = symbol::null);
|
|
|
|
/**
|
|
\brief Create a copy of the given rule.
|
|
*/
|
|
rule * mk(rule const * source, symbol const& name = symbol::null);
|
|
|
|
/** make sure there are not non-quantified variables that occur only in interpreted predicates */
|
|
void fix_unbound_vars(rule_ref& r, bool try_quantifier_elimination);
|
|
|
|
/**
|
|
\brief add proof that new rule is obtained by rewriting old rule.
|
|
*/
|
|
void mk_rule_rewrite_proof(rule& old_rule, rule& new_rule);
|
|
|
|
/**
|
|
\brief tag rule as asserted.
|
|
*/
|
|
void mk_rule_asserted_proof(rule& r);
|
|
|
|
/**
|
|
\brief apply substitution to variables of rule.
|
|
*/
|
|
void substitute(rule_ref& r, unsigned sz, expr*const* es);
|
|
|
|
/**
|
|
\brief Check that head :- tail[0], ..., tail[n-1]
|
|
is a valid Datalog rule.
|
|
*/
|
|
void check_valid_rule(app * head, unsigned n, app * const * tail) const;
|
|
|
|
/**
|
|
\brief Check that \c head may occur as a Datalog rule head.
|
|
*/
|
|
void check_valid_head(expr * head) const;
|
|
|
|
/**
|
|
\brief Return true if \c head may occur as a fact.
|
|
*/
|
|
bool is_fact(app * head) const;
|
|
|
|
static bool is_forall(ast_manager& m, expr* e, quantifier*& q);
|
|
|
|
rule_counter& get_counter() { return m_counter; }
|
|
|
|
app_ref ensure_app(expr* e);
|
|
|
|
void to_formula(rule const& r, expr_ref& result);
|
|
|
|
std::ostream& display_smt2(rule const& r, std::ostream & out);
|
|
|
|
bool has_uninterpreted_non_predicates(rule const& r, func_decl*& f) const;
|
|
void has_quantifiers(rule const& r, bool& existential, bool& universal, bool& lam) const;
|
|
bool has_quantifiers(rule const& r) const;
|
|
bool is_finite_domain(rule const& r) const;
|
|
|
|
};
|
|
|
|
class rule : public accounted_object {
|
|
friend class rule_manager;
|
|
|
|
app* m_head = nullptr;
|
|
proof* m_proof = nullptr;
|
|
unsigned m_tail_size = 0;
|
|
unsigned m_ref_cnt = 0;
|
|
unsigned m_positive_cnt = 0;
|
|
unsigned m_uninterp_cnt = 0;
|
|
symbol m_name;
|
|
/**
|
|
The following field is an array of tagged pointers.
|
|
- Tag 0: the atom is not negated
|
|
- Tag 1: the atom is negated.
|
|
|
|
The order of tail formulas is the following:
|
|
|
|
uninterpreted positive,
|
|
uninterpreted negative,
|
|
interpreted.
|
|
|
|
The negated flag is never set for interpreted tails.
|
|
*/
|
|
app * m_tail[0];
|
|
|
|
static unsigned get_obj_size(unsigned n) { return sizeof(rule) + n * sizeof(app *); }
|
|
|
|
rule() : m_ref_cnt(0), m_name(symbol::null) {}
|
|
|
|
void deallocate(ast_manager & m);
|
|
|
|
void get_used_vars(used_vars& uv) const;
|
|
|
|
public:
|
|
|
|
proof * get_proof() const { return m_proof; }
|
|
|
|
void set_proof(ast_manager& m, proof* p);
|
|
|
|
app * get_head() const { return m_head; }
|
|
|
|
func_decl* get_decl() const { return get_head()->get_decl(); }
|
|
|
|
unsigned get_tail_size() const { return m_tail_size; }
|
|
|
|
/**
|
|
\brief Return number of positive uninterpreted predicates in the tail.
|
|
|
|
These predicates are the first in the tail.
|
|
*/
|
|
unsigned get_positive_tail_size() const { return m_positive_cnt; }
|
|
unsigned get_uninterpreted_tail_size() const { return m_uninterp_cnt; }
|
|
|
|
/**
|
|
\brief Return i-th tail atom. The first \c get_uninterpreted_tail_size()
|
|
atoms are uninterpreted and the first \c get_positive_tail_size() are
|
|
uninterpreted and non-negated.
|
|
*/
|
|
app * get_tail(unsigned i) const { SASSERT(i < m_tail_size); return UNTAG(app *, m_tail[i]); }
|
|
|
|
func_decl* get_decl(unsigned i) const { SASSERT(i < get_uninterpreted_tail_size()); return get_tail(i)->get_decl(); }
|
|
|
|
bool is_neg_tail(unsigned i) const { SASSERT(i < m_tail_size); return GET_TAG(m_tail[i]) == 1; }
|
|
|
|
/**
|
|
Check whether predicate p is in the interpreted tail.
|
|
|
|
If only_positive is true, only the positive predicate tail atoms are checked.
|
|
*/
|
|
bool is_in_tail(const func_decl * p, bool only_positive=false) const;
|
|
|
|
bool has_negation() const;
|
|
|
|
/**
|
|
\brief Store in d the (direct) dependencies of the given rule.
|
|
*/
|
|
|
|
void norm_vars(rule_manager & rm);
|
|
|
|
void get_vars(ast_manager& m, ptr_vector<sort>& sorts) const;
|
|
|
|
void display(context & ctx, std::ostream & out) const;
|
|
|
|
/**
|
|
\brief Return the name(s) associated with this rule. Plural for preprocessed (e.g. obtained by inlining) rules.
|
|
|
|
This possibly returns a ";"-separated list of names.
|
|
*/
|
|
symbol const& name() const { return m_name; } ;
|
|
|
|
unsigned hash() const;
|
|
|
|
};
|
|
|
|
struct rule_eq_proc {
|
|
bool operator()(const rule * r1, const rule * r2) const;
|
|
};
|
|
struct rule_hash_proc {
|
|
unsigned operator()(const rule * r) const;
|
|
};
|
|
|
|
};
|
|
|
|
|