-
Notifications
You must be signed in to change notification settings - Fork 273
Improved invariants #1063
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
Improved invariants #1063
Conversation
@NathanJPhillips requested review also (can't do it formally for some reason) |
c0fea89
to
f199e65
Compare
src/util/invariant.cpp
Outdated
/// \param reason : C string giving the reason why the invariant should be true. | ||
void check_invariant( | ||
/// \param out: Stream to print backtrace | ||
void print_backtrace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate this should have gone in a previous review - but I notice the backtrace only happens under glibc
. Given that we currently run the DB platform on musl
based platform this seems like not a good limitation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik, all images have been moved to debian in the meantime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solution works on gcc and Windows: http://stacktrace.sourceforge.net/
Even if you don't use it it might be worth adding a comment mentioning it in the #else
section below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The eternal question of external deps. I'll leave this for subsequent work.
src/cbmc/cbmc_parse_options.cpp
Outdated
@@ -97,6 +97,31 @@ void cbmc_parse_optionst::eval_verbosity() | |||
ui_message_handler.set_verbosity(v); | |||
} | |||
|
|||
/// An example of structured invariants-- this contains fields to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest to put these tests into a unit test instead of cluttering the tool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just copying the existing clutter :) but I agree will move
src/util/invariant.cpp
Outdated
/// \param reason : C string giving the reason why the invariant should be true. | ||
void check_invariant( | ||
/// \param out: Stream to print backtrace | ||
void print_backtrace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
afaik, all images have been moved to debian in the meantime
src/util/invariant.h
Outdated
explicit invariant_failedt(const std::string& what) : logic_error(what) {} | ||
explicit invariant_failedt(const char* what) : logic_error(what) {} | ||
invariant_failedt( | ||
const std::string& what): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&what
src/util/invariant.h
Outdated
explicit invariant_failedt(const char* what) : logic_error(what) {} | ||
invariant_failedt( | ||
const std::string& what): | ||
logic_error(what), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent
src/util/invariant.h
Outdated
} | ||
|
||
/// Stores the backtrace set by `invariant_violated`. | ||
std::string backtrace; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe make protected?
src/util/invariant.h
Outdated
} | ||
|
||
/// Temporary storage for `what`'s return value | ||
mutable std::string what_str; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't this be declared locally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string needs to live beyond the lifespan of what()
. It's a bad interface; the docs for std::logic_error
admit as much, but I'd rather produce this on demand than inevitably have some derived class mutator forget to update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. I see. exception::what returns a pointer.
src/util/invariant.h
Outdated
@@ -10,6 +10,8 @@ Author: Martin Brain, [email protected] | |||
#define CPROVER_UTIL_INVARIANT_H | |||
|
|||
#include <stdexcept> | |||
#include <type_traits> | |||
#include <iostream> | |||
|
|||
/* | |||
** Invariants document conditions that the programmer believes to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/* */
should not be used
f199e65
to
1352369
Compare
Applied @peterschrammel changes except for moving the tests, which needs me to understand how unit tests are being done... |
@smowton as discussed, since you want to check the output of However, if you would also like to write a unit test (e.g. to check that under default builds, Example unit tests from variable sensitivity domain |
src/util/invariant.h
Outdated
/// \param reason : (derived from invariant_failedt) error report structure; | ||
/// its backtrace member will be set before it is used. | ||
template<class T> | ||
typename std::enable_if<std::is_base_of<invariant_failedt, T>::value>::type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to use templates here rather than just const invariant_failedt &
Unless I'm not understanding something, this will only compile with children of invariant_failedt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see - there is some magic to make different methods exist if the type is a string
or a invariant_failedt
. Can this not be done through regular method overloading, one that takes an const invariant_failedt &
and one that takes const std::string &
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think it probably could work like that in this case. Either taking a const invariant_failedt &
and then copying it to a temporary or else taking an invariant_failedt &
and having it in the contract that it will be updated with the stacktrace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took the true type because when we throw them we'll want to throw the actual type. If I throw an invariant_failedt &
then I will attempt to copy-construct an invariant_failedt
at the throw site (throws are by value). Such a copy constructor would discard information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see - I didn't know that. I suppose this is cleaner than a virtual clone method on invariant_failedt
src/util/invariant.cpp
Outdated
/// \param reason : C string giving the reason why the invariant should be true. | ||
void check_invariant( | ||
/// \param out: Stream to print backtrace | ||
void print_backtrace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This solution works on gcc and Windows: http://stacktrace.sourceforge.net/
Even if you don't use it it might be worth adding a comment mentioning it in the #else
section below.
src/util/invariant.h
Outdated
class invariant_failedt: public std::logic_error | ||
{ | ||
/// Stores the backtrace set by `invariant_violated`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally I would explicitly write private:
rather than relying on the default in case someone adds something public above.
src/util/invariant.h
Outdated
invariant_failedt( | ||
const std::string &what): | ||
logic_error(what), | ||
backtrace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary call to default constructor.
src/util/invariant.h
Outdated
{ \ | ||
if(!(CONDITION)) \ | ||
invariant_violated(__FILE__, __FUNCTION__, __LINE__, (REASON)); \ | ||
} while(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably this is to make a statement that requires a trailing semicolon to make INVARIANT behave syntactically as much like a normal function as possible. If so, a comment saying so would be helpful.
src/util/invariant.h
Outdated
/// \param reason : (derived from invariant_failedt) error report structure; | ||
/// its backtrace member will be set before it is used. | ||
template<class T> | ||
typename std::enable_if<std::is_base_of<invariant_failedt, T>::value>::type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think it probably could work like that in this case. Either taking a const invariant_failedt &
and then copying it to a temporary or else taking an invariant_failedt &
and having it in the contract that it will be updated with the stacktrace.
src/util/invariant.h
Outdated
/// Set the backtrace stored against this error report. | ||
/// This is overwritten by `invariant_violated`, which is called | ||
/// from the `INVARIANT` family of macros. | ||
void set_backtrace(const std::string &_backtrace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider having this private (probably actually directly accessing the private field) and making invariant_violated
a friend or a static member of this class.
@thk123 under every build they abort with this patch, but if we change that I will use Catch as you recommend |
1352369
to
ad188a1
Compare
@NathanJPhillips @peterschrammel all changes applied except as noted |
ad188a1
to
e449526
Compare
All changes applied, including moving the tests. @thk123 @peterschrammel @NathanJPhillips ready for re-review. |
regression/invariants/Makefile
Outdated
include ../../src/common | ||
|
||
test: driver$(EXEEXT) | ||
@if ! ../test.pl -c ../../../src/cbmc/cbmc ; then \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this work? Don't you need some shell script that compiles and runs driver.cpp
rather than running cbmc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can no longer claim I understand what's going on here. Files being compiled but seemingly never used (driver.cpp), options seemingly nowhere handled (test-invariant-failure). Can we keep it simple, please?
regression/invariants/driver.cpp
Outdated
|
||
/// Causes an invariant failure dependent on first argument value. | ||
/// Returns 1 on unexpected arguments. | ||
int main(int argc, char** argv) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit picking: Place {
on next line
src/cbmc/cbmc_parse_options.cpp
Outdated
@@ -106,26 +106,6 @@ void cbmc_parse_optionst::get_command_line_options(optionst &options) | |||
} | |||
|
|||
// Test only; do not use for input validation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that comment should be removed as well
src/cbmc/cbmc_parse_options.h
Outdated
@@ -63,7 +63,7 @@ class optionst; | |||
"(java-cp-include-files):" \ | |||
"(localize-faults)(localize-faults-method):" \ | |||
"(lazy-methods)" \ | |||
"(test-invariant-failure)" \ | |||
"(test-invariant-failure):" \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I admit I have no idea how this works: all code handling "test-invariant-failure" has been removed. So where is it being processed?
e449526
to
a7f31e2
Compare
@thk123 @tautschnig oops I had not committed some changes but was getting away with it due to a stale cbmc. Changed now, but in summary:
|
a7f31e2
to
a61e740
Compare
@tautschnig that option should have been removed; all your changes now applied. |
a61e740
to
3871f8f
Compare
src/util/invariant.cpp
Outdated
/// \return backtrace with a file / function / line header. | ||
std::string get_backtrace( | ||
const char * const file, | ||
const char * const function, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are C strings being used here instead of std::string
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constant strings introduced by the preprocessor I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then the compiler would happily build const std::string
objects from those?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I was just saying I guess @martin-cs presumably did that cos he knew they were always plain old string constants. I can make them std::string if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am aware that this implies further changes; but given the effort by @reuk to get rid of plain pointers we should stick with that direction.
src/util/invariant.h
Outdated
@@ -10,6 +10,8 @@ Author: Martin Brain, [email protected] | |||
#define CPROVER_UTIL_INVARIANT_H | |||
|
|||
#include <stdexcept> | |||
#include <type_traits> | |||
#include <iostream> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We must avoid #include <iostream>
in a widely used header file to keep compilation times reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's just for forward declarations #include <iosfwd>
might be acceptable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to follow-up on that: the implementation of the function using std::cerr
would thus have to go in the cpp file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved the std::cerr
stuff into the cpp file
80944ef
to
9c8a2be
Compare
Last fix required: http://cbmc-linter.diffblue.com.s3-website-us-east-1.amazonaws.com/linter-b1a89d91dd4441085f467f642e3c449ddf718f1c.txt |
b1a89d9
to
83d9b04
Compare
(fix applied) |
@kroening, this is ready to go. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a couple of issues with the structure of invariant_failedt, sorry to revive this PR after so long.
src/util/invariant.h
Outdated
|
||
/// Describes this error using `backtrace` and `pretty_print`. | ||
/// Cannot be overridden; override `pretty_print` instead. | ||
#ifdef _MSC_VER |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a macro in util
called CBMC_NOEXCEPT
or similar which expands to noexcept
on platforms which support it, and nothing on those which don't. We could do the same thing with constexpr
too.
That way, this function declaration would become
const char *what() const CBMC_NOEXCEPT final
which is a bit cleaner.
src/util/invariant.h
Outdated
/// structured error description. `std::logic_error::what` is | ||
/// overridden to use this and the backtrace to describe the | ||
/// error. | ||
virtual std::string pretty_print() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exceptions already have a virtual const function which returns a string, so I'm not convinced there's a need for another. I would restructure this code like so:
class invariant_failedt : public std::logic_error {
// We don't need a mutable string holding the return of `what`, so the
// object is smaller *and* more const-safe
std::string backtrace;
public:
// This is not virtual! It will call `what` and add a stack trace
std::string pretty_print() const {
try {
std::ostringstream ss;
ss << what() << '\n' + backtrace;
return ss.str();
} catch (...) {
return "Exception describing invariant failure";
}
}
// We don't even need to override `what` here, but derived classes are free
// to do so if they need more structured info.
};
A possible downfall of this approach is that clients need to know to call pretty_print
when they catch the exception, rather than what
, but I believe this is a reasonable trade-off, as the invariant_failedt no longer needs to store the what_str member, making it smaller and more const-safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe various standard things do call what()
- in fact isn't the standard behaviour on uncaught exceptions to print what()
and terminate?
I've also got reuk's comments to account for, will do this tomorrow morning (on the road today) |
Great, thanks! |
Looks ready to go after rebase. |
a4df2b3
to
0c29208
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
src/util/invariant.h
Outdated
#define INVARIANT(CONDITION, REASON) do {} while(0) | ||
|
||
#define INVARIANT(CONDITION, REASON, ...) do {} while(0) | ||
#define INVARIANT(CONDITION, REASON, ...) do {} while(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this duplication deliberate?
const std::string reason; | ||
|
||
invariant_failedt( | ||
const std::string &_file, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor point: names starting with an underscore are reserved in the global namespace, and names starting with _[A-Z]
are reserved everywhere. Although the usage here is not in violation, I'd prefer to avoid underscore prefixes in general.
48781ac
to
06f758e
Compare
@kroening ready when you are |
06f758e
to
493e9be
Compare
[depends: #1063] Use nullptr to represent null pointers (targets master)
Revert "[depends: #1063] Use nullptr to represent null pointers (targets master)"
Corrected simplify lhs in ai_domain_baset The boolean flag was the wrong way round for ai_simplify_lhs - this corrects this. Added unit tests to validate the return meaning of ai_simplify_lhs These tests use a mock implementation of the `ai_domain_baset` interface just to validate that true means no simplification. Fix nullptr Fix linter errors, ignoring big-int and miniz Allow invariants with structured exceptions Revert "[depends: diffblue#1063] Use nullptr to represent null pointers (targets master)" goto-symex^2: goto-symex for symbolic execution Goto merged before jumping out the goto-symex operator loop, for handling GOTO and ASSERT
Based on recent discussions about the shortcomings of the
INVARIANT
family of macros, here are some improvements:INVARIANT(false, "Hello world")
still works and produces aninvariant_failedt
as before, but one can also derive frominvariant_failedt
, add fields to describe an error in a more useful way, and overridepretty_print
to render them into a human-readable error message. This means that even before catching invariants becomes allowed practice we can be producing structured invariants ready for if and when that changes. The update to tests demonstrates this feature in use.catch(...)
may accidentally swallow in invariant it didn't mean to.reason
expression, whether structured or a simple string, is now lazily evaluated (e.g.INVARIANT(x==y, str1+str2)
will only perform the string concatenation if the test fails).Please get in there now with your design concerns rather than objecting to down-the-line PRs that use these features!