This implements several variants of lazy proof checking in the core proof checker.
Note this extends the ProofNode class with an additional Boolean d_provenChecked indicating whether the d_proven field was checked by the underlying proof checker.
This PR updates the default proof checking mode to lazy. The previous default can now be enabled by --proof-check=eager-simple.
help = "assertion failure for any incorrect rule application or untrusted lemma having pedantic level <=N with proof"
[[option]]
- name = "proofEagerChecking"
+ name = "proofCheck"
category = "regular"
- long = "proof-eager-checking"
- type = "bool"
- default = "false"
- help = "check proofs eagerly with proof for local debugging"
+ long = "proof-check=MODE"
+ type = "ProofCheckMode"
+ default = "LAZY"
+ help = "select proof checking mode"
+ help_mode = "Proof checking modes."
+[[option.mode.EAGER]]
+ name = "eager"
+ help = "check rule applications and proofs from generators eagerly for local debugging"
+[[option.mode.EAGER_SIMPLE]]
+ name = "eager-simple"
+ help = "check rule applications during construction"
+[[option.mode.LAZY]]
+ name = "lazy"
+ help = "check rule applications only during final proof construction"
+[[option.mode.NONE]]
+ name = "none"
+ help = "do not check rule applications"
[[option]]
name = "proofGranularityMode"
<< " set to generator " << pg->identify() << "\n";
// note this will rewrite the generator for expected, if any
d_gens.insert(expected, pg);
- // check if chain is closed if options::proofEagerChecking() is on
- if (options::proofEagerChecking())
+ // check if chain is closed if eager checking is on
+ if (options::proofCheck() == options::ProofCheckMode::EAGER)
{
Trace("lazy-cdproofchain")
<< "LazyCDProofChain::addLazyStep: Checking closed proof...\n";
{
}
-ProofChecker::ProofChecker(uint32_t pclevel, rewriter::RewriteDb* rdb)
- : d_pclevel(pclevel), d_rdb(rdb)
+ProofChecker::ProofChecker(bool eagerCheck,
+ uint32_t pclevel,
+ rewriter::RewriteDb* rdb)
+ : d_eagerCheck(eagerCheck), d_pclevel(pclevel), d_rdb(rdb)
{
}
}
}
// fails if pedantic level is not met
- if (options::proofEagerChecking())
+ if (d_eagerCheck)
{
std::stringstream serr;
if (isPedanticFailure(id, serr, enableOutput))
class ProofChecker
{
public:
- ProofChecker(uint32_t pclevel = 0, rewriter::RewriteDb* rdb = nullptr);
+ ProofChecker(bool eagerCheck,
+ uint32_t pclevel = 0,
+ rewriter::RewriteDb* rdb = nullptr);
~ProofChecker() {}
/**
* Return the formula that is proven by proof node pn, or null if pn is not
std::map<PfRule, ProofRuleChecker*> d_checker;
/** Maps proof trusted rules to their pedantic level */
std::map<PfRule, uint32_t> d_plevel;
+ /** Whether we check for pedantic failures eagerly */
+ bool d_eagerCheck;
/** The pedantic level of this checker */
uint32_t d_pclevel;
/** Pointer to the rewrite database */
return;
}
bool isTraceDebug = Trace.isOn(c);
- if (!options::proofEagerChecking() && !isTraceDebug)
+ if (options::proofCheck() != options::ProofCheckMode::EAGER && !isTraceDebug)
{
// trace is off and proof new eager checking is off, do not do check
return;
ProofNode::ProofNode(PfRule id,
const std::vector<std::shared_ptr<ProofNode>>& children,
const std::vector<Node>& args)
+ : d_provenChecked(false)
{
setValue(id, children, args);
}
std::vector<Node> d_args;
/** The cache of the fact that has been proven, modifiable by ProofChecker */
Node d_proven;
+ /** Was d_proven actually checked, or is it trusted? */
+ bool d_provenChecked;
};
inline size_t ProofNodeHashFunction::operator()(
ProofNodeManager::ProofNodeManager(ProofChecker* pc) : d_checker(pc)
{
d_true = NodeManager::currentNM()->mkConst(true);
+ // we always allocate a proof checker, regardless of the proof checking mode
+ Assert(d_checker != nullptr);
}
std::shared_ptr<ProofNode> ProofNodeManager::mkNode(
{
Trace("pnm") << "ProofNodeManager::mkNode " << id << " {" << expected.getId()
<< "} " << expected << "\n";
- Node res = checkInternal(id, children, args, expected);
+ bool didCheck = false;
+ Node res = checkInternal(id, children, args, expected, didCheck);
if (res.isNull())
{
// if it was invalid, then we return the null node
std::shared_ptr<ProofNode> pn =
std::make_shared<ProofNode>(id, children, args);
pn->d_proven = res;
+ pn->d_provenChecked = didCheck;
return pn;
}
{
return false;
}
+ // copy whether we did the check
+ pn->d_provenChecked = pnr->d_provenChecked;
// can shortcut re-check of rule
return updateNodeInternal(
pn, pnr->getRule(), pnr->getChildren(), pnr->getArguments(), false);
}
+void ProofNodeManager::ensureChecked(ProofNode* pn)
+{
+ if (pn->d_provenChecked)
+ {
+ // already checked
+ return;
+ }
+ // directly call the proof checker
+ Node res = d_checker->check(pn, pn->getResult());
+ pn->d_provenChecked = true;
+ // should have the correct result
+ Assert(res == pn->d_proven);
+}
+
Node ProofNodeManager::checkInternal(
PfRule id,
const std::vector<std::shared_ptr<ProofNode>>& children,
const std::vector<Node>& args,
- Node expected)
+ Node expected,
+ bool& didCheck)
{
- Node res;
- if (d_checker)
+ // if the user supplied an expected result, then we trust it if we are in
+ // a proof checking mode that does not eagerly check rule applications
+ if (!expected.isNull())
{
- // check with the checker, which takes expected as argument
- res = d_checker->check(id, children, args, expected);
- Assert(!res.isNull())
- << "ProofNodeManager::checkInternal: failed to check proof";
- }
- else
- {
- // otherwise we trust the expected value, if it exists
- Assert(!expected.isNull()) << "ProofNodeManager::checkInternal: no checker "
- "or expected value provided";
- res = expected;
+ if (options::proofCheck() == options::ProofCheckMode::LAZY
+ || options::proofCheck() == options::ProofCheckMode::NONE)
+ {
+ return expected;
+ }
}
+ // check with the checker, which takes expected as argument
+ Node res = d_checker->check(id, children, args, expected);
+ didCheck = true;
+ Assert(!res.isNull())
+ << "ProofNodeManager::checkInternal: failed to check proof";
return res;
}
visited[cur] = cloned;
// we trust the above cloning does not change what is proven
cloned->d_proven = cur->d_proven;
+ cloned->d_provenChecked = cur->d_provenChecked;
}
}
Assert(visited.find(orig) != visited.end());
{
Assert(pn != nullptr);
// ---------------- check for cyclic
- if (options::proofEagerChecking())
+ if (options::proofCheck() == options::ProofCheckMode::EAGER)
{
std::unordered_set<const ProofNode*> visited;
for (const std::shared_ptr<ProofNode>& cpc : children)
if (needsCheck)
{
// We expect to prove the same thing as before
- Node res = checkInternal(id, children, args, pn->d_proven);
+ bool didCheck = false;
+ Node res = checkInternal(id, children, args, pn->d_proven, didCheck);
if (res.isNull())
{
// if it was invalid, then we do not update
}
// proven field should already be the same as the result
Assert(res == pn->d_proven);
+ pn->d_provenChecked = didCheck;
}
// we update its value
* unchanged.
*/
bool updateNode(ProofNode* pn, ProofNode* pnr);
+ /**
+ * Ensure that pn is checked, regardless of the proof check format.
+ */
+ void ensureChecked(ProofNode* pn);
/** Get the underlying proof checker */
ProofChecker* getChecker() const;
/**
* This throws an assertion error if we fail to check such a proof node, or
* if expected is provided (non-null) and is different what is proven by the
* other arguments.
+ *
+ * The flag didCheck is set to true if the underlying proof checker was
+ * invoked. This may be false if e.g. the proof checking mode is lazy.
*/
Node checkInternal(PfRule id,
const std::vector<std::shared_ptr<ProofNode>>& children,
const std::vector<Node>& args,
- Node expected);
+ Node expected,
+ bool& didCheck);
/**
* Update node internal, return true if successful. This is called by
* the update node methods above. The argument needsCheck is whether we
Assert(ppSkolems.size() == ppLemmas.size());
// do final checks on the lemmas we are about to send
- if (isProofEnabled() && options::proofEagerChecking())
+ if (isProofEnabled()
+ && options::proofCheck() == options::ProofCheckMode::EAGER)
{
Assert(tplemma.getGenerator() != nullptr);
// ensure closed, make the proof node eagerly here to debug
}
} while (expanded);
// now we should be able to close it
- if (options::proofEagerChecking())
+ if (options::proofCheck() == options::ProofCheckMode::EAGER)
{
std::vector<Node> assumptionsVec;
for (const Node& a : d_assumptions)
void PreprocessProofGenerator::checkEagerPedantic(PfRule r)
{
- if (options::proofEagerChecking())
+ if (options::proofCheck() == options::ProofCheckMode::EAGER)
{
// catch a pedantic failure now, which otherwise would not be
// triggered since we are doing lazy proof generation
{
PfRule r = pn->getRule();
// if not doing eager pedantic checking, fail if below threshold
- if (!options::proofEagerChecking())
+ if (options::proofCheck() != options::ProofCheckMode::EAGER)
{
if (!d_pedanticFailure)
{
}
}
}
+ if (options::proofCheck() != options::ProofCheckMode::NONE)
+ {
+ d_pnm->ensureChecked(pn.get());
+ }
uint32_t plevel = d_pnm->getChecker()->getPedanticLevel(r);
if (plevel != 0)
{
PfManager::PfManager(Env& env, SmtEngine* smte)
: d_env(env),
- d_pchecker(new ProofChecker(options::proofPedantic())),
+ d_pchecker(new ProofChecker(
+ d_env.getOptions().proof.proofCheck == options::ProofCheckMode::EAGER,
+ d_env.getOptions().proof.proofPedantic)),
d_pnm(new ProofNodeManager(d_pchecker.get())),
d_pppg(new PreprocessProofGenerator(
d_pnm.get(), env.getUserContext(), "smt::PreprocessProofGenerator")),
<< reasonNoProofs.str() << "." << std::endl;
opts.smt.produceProofs = false;
opts.smt.checkProofs = false;
- opts.proof.proofEagerChecking = false;
}
}
}
// Check that UNSAT results generate a proof correctly.
if (d_env->getOptions().smt.checkProofs
- || d_env->getOptions().proof.proofEagerChecking)
+ || d_env->getOptions().proof.proofCheck
+ == options::ProofCheckMode::EAGER)
{
if (r.asSatisfiabilityResult().isSat() == Result::UNSAT)
{
if ((d_env->getOptions().smt.checkProofs
- || d_env->getOptions().proof.proofEagerChecking)
+ || d_env->getOptions().proof.proofCheck
+ == options::ProofCheckMode::EAGER)
&& !d_env->getOptions().smt.produceProofs)
{
throw ModalException(
// internal check the proof
PropEngine* pe = getPropEngine();
Assert(pe != nullptr);
- if (d_env->getOptions().proof.proofEagerChecking)
+ if (d_env->getOptions().proof.proofCheck == options::ProofCheckMode::EAGER)
{
pe->checkProof(d_asserts->getAssertionList());
}
// disable all proof options
coreChecker->getOptions().smt.produceProofs = false;
coreChecker->getOptions().smt.checkProofs = false;
- coreChecker->getOptions().proof.proofEagerChecking = false;
for (const Node& ucAssertion : core)
{
// disable all proof options
coreChecker->getOptions().smt.produceProofs = false;
coreChecker->getOptions().smt.checkProofs = false;
- coreChecker->getOptions().proof.proofEagerChecking = false;
// set up separation logic heap if necessary
TypeNode sepLocType, sepDataType;
-; COMMAND-LINE: --proof-eager-checking
+; COMMAND-LINE: --proof-check=eager
; EXPECT: sat
(set-logic QF_UFLRA)
(declare-fun v1 () Real)