Skip to content

Allow checks to be performed inside quantifier statements. #6399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 15, 2021

Conversation

NlightNFotis
Copy link
Contributor

This allows checks from CLI flags like --pointer-check to be performed inside
expressions in quantifier statements.

This should be addressing issue #6231 .

  • Each commit message has a non-empty body, explaining why the change was made.
  • Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • White-space or formatting changes outside the feature-related changed lines are in commits of their own.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't immediately see anything wrong with this BUT the whole thing makes me uneasy. The comments have been there since 2013 which makes me think that they were probably there for a reason. My immediate thought is about scoping and whether the quantified variable is actually live when the variable is checked. I am also unsure about how nested quantifiers are going to work and to what degree the guards are applied to the checks as well.

Can you please say a little more about how you are expecting things to work?

@codecov
Copy link

codecov bot commented Oct 18, 2021

Codecov Report

Merging #6399 (e9585bd) into develop (5f33908) will increase coverage by 0.00%.
The diff coverage is 92.30%.

❗ Current head e9585bd differs from pull request most recent head bc772e4. Consider uploading reports for the commit bc772e4 to get more accurate results
Impacted file tree graph

@@           Coverage Diff            @@
##           develop    #6399   +/-   ##
========================================
  Coverage    75.98%   75.98%           
========================================
  Files         1578     1578           
  Lines       180910   180931   +21     
========================================
+ Hits        137467   137487   +20     
- Misses       43443    43444    +1     
Impacted Files Coverage Δ
src/analyses/goto_check_c.cpp 90.29% <92.30%> (-0.10%) ⬇️
src/goto-instrument/contracts/contracts.cpp 95.11% <0.00%> (+0.07%) ⬆️
src/solvers/smt2/smt2_dec.cpp 76.52% <0.00%> (+0.86%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eeddb3f...bc772e4. Read the comment docs.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more concrete requests for other functionality.

Copy link
Member

@kroening kroening left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens to work for the specific case of positive forall, since this is doing Skolemisation implicitly.

  1. This will fail (generating VCCs that are too strong) for exists.
  2. I would much prefer that the Skolemisation is done explicitly, i.e., by introducing a Skolem constant and instantiating the quantifier with it.

@NlightNFotis
Copy link
Contributor Author

Hi Daniel @kroening.

With regards to your first query, we have pushed some changes that show that existentially quantified statements appear to be working as they should (or at least, how we expect them to). Could you review these and let us know if these much your expectations, or if there's a counterexample case you could think of that would help us test our change in a way that exposes a weakness of our current implementation?

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Do you have examples of quantifiers in assumptions?
  2. Can you briefly outline how this works? What logical expression is generated? How does this interact with scoping?

Copy link
Contributor

@SaswatPadhi SaswatPadhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this! This would help us catch issues in code contracts. Earlier, invariants and function contracts that used implications but performed incorrect/unsafe operations were not caught.

All test cases in this PR seem to check that the consequent is safe. May be the fix already checks the antecedent as well, but can we also add a regression test for that? Something like:

__CPROVER_forall {
  int i;
  (0 <= i && i <= (n / 0))    /* (n/0) should be caught by `--div-by-zero` */
  ==> ...
}

@martin-cs
Copy link
Collaborator

@SaswatPadhi makes a good point. It is not clear to me whether this treats quantified expressions as guard/statement pairs and if so, whether it checks in both, does it use the guard for the checks in the statement?

@NlightNFotis NlightNFotis force-pushed the fix_6231 branch 3 times, most recently from bbdcd3f to ff47efb Compare October 29, 2021 15:26
@NlightNFotis
Copy link
Contributor Author

@SaswatPadhi makes a good point. It is not clear to me whether this treats quantified expressions as guard/statement pairs and if so, whether it checks in both, does it use the guard for the checks in the statement?

Hi Martin, yes, it does treat quantified expressions as guard/statement pairs and yes it uses the guard for the checks in the statement. I have factored the change that does that in its own commit, with an explanation of how it works there.

As a shorter explanation here, goto_checkt::check in the same file calls check_rec on the expression with true_exprt as a guard, which is then refined based on the exact nature of the expression. We already have support for refining the guard for logical operators, but we were dispatching to that only if the expression had expr.id() == ID_and or expr.id() == ID_or. This appears to be a latent bug that was not manifest because we didn't have any expressions of the particular form that the tests submitted for this PR, so we didn't observe that. As soon as we started performing checks for quantifier expressions (basically, not immediately bailing upon seeing one), we hit this bug.

Does that answer your question?

@NlightNFotis
Copy link
Contributor Author

Hi @kroening ,

I'm trying to better understand the requirement of the second part of your comment, which I quote:

I would much prefer that the Skolemisation is done explicitly, i.e., by introducing a Skolem constant and instantiating the quantifier with it.

My understanding is that Skolemization is being done the same way it was being done before this patch, i.e. in goto_symext::symex_assert() of src/goto-symex/symex_main.cpp:157, which appears to convert existential quantifiers into universals (the first part of the Skolemization process) and then performs a renaming of the bound variable (which performs the substitution by a Skolem function/Skolem constant).

Is that not adequate in some capacity? We have performed extended testing and it seems like this bug fix is working as intended - but if you believe that there's a chance some input might fail we're happy to investigate further and tighten the coverage of the tests for this.

Or is your request that we lift this all into a separate module? If yes, where do you think this would be better plugged in - the simplification modules, a completely different pass over the goto-program that hooks into the transformation pipeline, or somewhere else entirely?

@martin-cs
Copy link
Collaborator

@NlightNFotis Thanks for your response, the explanation does indeed help and yes, I think you are correct about this being a latent bug. Would it be possible to write a test case that doesn't use quantifiers that triggers this? Would something like y != 0 ==> 10/y <= 10 and --div-by-zero-check do? That way the fixes could be split from the new feature and be approved even if there is more discussion over the checks in quantifiers.

Please could you have a look at, check and if necessary update:

/// Check a logical operation: check each operand in separation while

guard.add(expr.id() == ID_or ? boolean_negate(op) : op);

( The real cherry on top would be adding a PRECONDITION that checks for the expression types that are intended to be supported. )

@kroening
Copy link
Member

kroening commented Nov 1, 2021

The problem is that the identifiers used in quantifiers are no longer guaranteed to be unique, i.e., you could have something like
i[2]== 2 && exists i:int. x/i
which will yield a check that reads
(i[2] != 2) => i!=0
In this formula, i is used twice, and isn't even type consistent -- the first instance is an array, and the second is an int. Formulas like that must not be generated.

You can't test that in the C front-end, since the C front-end's treatment of quantifiers pre-dates the introduction of scoping in the solver backends. Please don't even think about reintroducing this restriction, not even as a temporary hack.

The way to do this properly is to introduce a Skolem symbol for the bound identifier. You'll find that the classes for exists and forall have convenient methods for making that easy!

@martin-cs
Copy link
Collaborator

@NlightNFotis while reviewing the changes you had kindly described I ran into some concerns. I have documented them here: #6423 I don't consider them a precondition for this PR, but, obviously, they are linked.

@NlightNFotis
Copy link
Contributor Author

@kroening Hi Daniel, the last commit is performing scolemisation of the formula in goto_check. Can you please review it now and let us know if this is adequate for what you had in mind?

@martin-cs Given that the bug-fix has now been merged on its own PR, could you please re-review this and let me know if there's anything more that needs be done?

@NlightNFotis
Copy link
Contributor Author

Hi @kroening, can you take a look at the PR now and let us know if it is more or less what you envisioned for skolemization as a pass?

Specifically, it's the last commit that implements this.

CPROVER_PREFIX,
id2string(ns.lookup(quantifier_sym.get_identifier()).base_name),
quantified_expr.source_location(),
ID_C,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still hard coded in your new rename_and_substitute function and still needs fixing.

@NlightNFotis
Copy link
Contributor Author

Hello @martin-cs and @kroening , would it be possible to have a look at this again?

It's past our internal reviews and blocked on your reviews right now. I want to take this forward, either in terms of performing improvements and revisions if deemed necessary, or getting this merged.

@martin-cs
Copy link
Collaborator

@NlightNFotis My apologies for the delay in reviewing. I will look ASAP.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am removing my block on this but I still have some concerns:

  • There are no tests using assume; that matters from a user point of view because of code contracts and also from a technical point of view as these are "flipped" during symex so what works with the SAT engine is different.
  • A lot of the issues with quantifiers are simplified when there is only one level of quantifier alternation. So I feel that alternating tests would be useful.
  • The renaming code is not, IMHO, Skolemization and to me, doesn't match what @kroening requested. However I believe that it fixes the immediate issue he raised and I will leave it to him to pursue that one.
  • I'm still struggling a bit to see what is generated; I /think/ it is something like

A && __CPROVER_forall { int i; B ==> C}

will create a VC:

assert(A && __CPROVER_forall { int i; B ==> CHECK_FROM_C } );

it would be good to get that confirmed.

^EXIT=10$
^SIGNAL=0$
^VERIFICATION FAILED$
line 9 dereference failure: pointer outside object bounds in a\[\(signed (long|long long) int\)i\]: FAILURE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to check that the assertion passes?

^EXIT=0$
^SIGNAL=0$
^VERIFICATION SUCCESSFUL$
line 9 dereference failure:.*SUCCESS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more complete check line as you have in other tests would be good.

@@ -0,0 +1,15 @@
CORE
smt_missing_range_check.c
--pointer-check -z3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we still don't have a CI run that doesn't have Z3 set up, please can this be tagged so that the regression tests still work on machines that don't have Z3 installed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in the last commit I have added a check for the z3 binary in CMakeLists.txt

^EXIT=0$
^SIGNAL=0$
^VERIFICATION SUCCESSFUL$
line 9 dereference failure:.*SUCCESS
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More complete matching against results would be good.


assert(*a == *a);

// BUG: no errors even with `--pointer-check` enabled -- now fixed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite sure what this comment is trying to tell me. Whatever it is, it feels like there might be a better place and way or saying it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I added this comment in a haste, and now realise that my phrasing was not the best 😞

I have rephrased that in commit 604d12c. Can you let me know if that's clearer?


assert(*a == *a);

// BUG: no errors even with `--pointer-check` enabled -- now fixed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

exprt &condition,
symbol_table_baset &symbol_table)
{
condition.visit_post([&symbol_table](exprt &sub_expression) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@@ -0,0 +1,57 @@
/// \file skolemization.h
/// Rename variables in existentially quantified statements.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not what I would think of as Skolemization; this is renaming. For me, Skolemization has to remove the existential so:

assert(__CPROVER_exists {  int i;   (0 <= i && i < 10) && a[i] == i *i   });

becomes:

int skolem_23_i;  // Note that if it is nested in a forall then it will need to be an uninterpreted function.
assert( (0 <= skolem_23_i && skolem_23_i < 10) && a[skolem_23_i] == skolem_23_i * skolem_23_i );

The general use-case for this is so that provers only have to deal with one kind of quantifier.

Note that the symex engine performs on-the-fly Skolemization where it can; that's how the SAT engine can handle quantifiers at all. However it dynamically handles polarity, so depending on whether the quantifiers are in assume or assert, negated, etc. it will do the right thing.

Copy link
Contributor Author

@NlightNFotis NlightNFotis Dec 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Martin,

Apologies for that - I'm aware of how skolemization works (in contrast to the code here), but when implemented in its proper form, it resulted in elimination of the existential quantifier far too early in the pipeline. There's code in goto-check or goto-symex (can't remember which of the two) - which depended on the quantifiers being there during VCC generation, and we were left with vccs that didn't semantically match the intended meaning, getting results that were all over the place.

The intended effect of the code above was to do a partial-skolemization, if we can admit that as a thing: perform the renaming, and let the existential quantifier stay, with the result being that the correct VCCs where generated, and it was being eliminated later in the pipeline in a way that felt safer (given that the variables were renamed before hand).

Definitely wasn't ideal, but there was a lot of uncertainty around the implementation of that feature in our pipeline.

My aim now is to drop this and substitute for Daniel's implementation (assuming it passes all tests).

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annoying -- github won't let me just remove my "Request changes" and return to a neutral state. So, I am "approving" this but please don't treat this approved and merge.

@kroening
Copy link
Member

Please look at https://github.com/diffblue/cbmc/tree/goto_check_guard for an alternative approach, which relegates the problem of introducing the Skolem functions to the solver.

A key benefit of the alternative approach is that this means that there won't be two different kinds of goto-programs, one where bound symbols are unique and one where they aren't.

@kroening kroening mentioned this pull request Dec 2, 2021
4 tasks
@NlightNFotis NlightNFotis force-pushed the fix_6231 branch 3 times, most recently from 06cc456 to 38c80f2 Compare December 9, 2021 18:02
@NlightNFotis
Copy link
Contributor Author

Hi @martin-cs and @kroening ,

as requested, I've made several enhancements to the tests (added the extra checks Martin requested, tests based on the behaviour of the quantifiers in assumes, and alternating quantifier tests - each of which are in their own commits) and changed the implementation to be using Daniel's code (I lost the commit information because I couldn't cleanly cherry-pick after some changes to goto-check, but I hope my commit message preserves authorship information in an acceptable way).

Could you please have a look again and let me know how it looks to you now?

@kroening
Copy link
Member

kroening commented Dec 9, 2021

May I suggest squashing the 9 commits that add/change the tests? Otherwise looks fine.

@NlightNFotis NlightNFotis force-pushed the fix_6231 branch 2 times, most recently from ae2e381 to 38c80f2 Compare December 13, 2021 10:41
@NlightNFotis
Copy link
Contributor Author

Hi @kroening, if all is fine are you happy to lift your blocking review?

I will of course squash all the test commits before the final merge, I just want to give a chance to @martin-cs to review them on a commit by commit basis so that he can more easily check if his concerns have been addressed accordingly, and assuming he's also happy I will squash and merge.

NlightNFotis and others added 4 commits December 15, 2021 14:05
These are checked against `--pointer-check` and one of them is
sourced from the code reported in diffblue#6231.
We are now producing more checks, which was messing with the expectations
of the tests. Now made it a bit more general and all is good again.
Original code by @kroening in branch `goto_check_guard`.
Original code by @kroening in `goto_check_guard` branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants