Download presentation
Presentation is loading. Please wait.
Published byPatrick Hansen Modified over 5 years ago
1
Preconditions, postconditions, invariants how they help write robust programs
Andrzej Krzemieński akrzemi1.wordpress.com
2
Agenda Preconditions, invariants: What they are How to use them
How the language can help
3
Introduction bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); }
4
Introduction What if a is null?
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } What if a is null?
5
Introduction bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; }
6
Introduction What if lo > hi?
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } What if lo > hi?
7
Introduction size_t length(const char* str);
8
Introduction size_t length(const char* str); What if str is null?
9
Introduction What if str is null? What if str does not end with '\0'?
size_t length(const char* str); What if str is null? What if str does not end with '\0'?
10
Contract
11
Contract bool is_even(int x) { return x % 2 == 0; }
12
Contract Wide contract – function works for any input value.
bool is_even(int x) { return x % 2 == 0; } Wide contract – function works for any input value.
13
Contract Wide contract – function works for any input value.
bool is_even(int x) { return x % 2 == 0; } Wide contract – function works for any input value. size_t length(const char* str); Narrow contract – some function inputs are invalid.
14
Contract bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); }
15
Contract Type Offer represents the concept we want to talk about.
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } Type Offer represents the concept we want to talk about. The pointer is used for some engineering reasons.
16
Contract C++ types are mixture of these two: Concept representations
Engineering ballast
17
Contract C++ programs are mixture of these two:
Representing “business logic” Some language-specific ballast
18
Contract bool is_better(Offer a, Offer b) { return a.income() > b.income(); }
19
Contract Type Offer may be non-copyable.
bool is_better(Offer a, Offer b) { return a.income() > b.income(); } Type Offer may be non-copyable. Copying it may be too expensive
20
Contract bool is_better(const Offer& a, const Offer& b) { return a.income() > b.income(); }
21
Contract A reference is already a ballast.
bool is_better(const Offer& a, const Offer& b) { return a.income() > b.income(); } A reference is already a ballast.
22
Contract A reference is already a ballast.
bool is_better(const Offer& a, const Offer& b) { return a.income() > b.income(); } A reference is already a ballast. More ballast required by context: vector<Offer*> offers; sort(offers, &is_better);
23
Contract Type-system-wise some values are possible.
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } Type-system-wise some values are possible. But they are meaningless in our business model.
24
Contract Contracts help define the gap between the business logic and the type- system.
25
Contract Contracts help define the gap between the business logic and the type- system. Business logic: an offer Type system: const class Offer*
26
Contract bool is_better(const Offer* a, const Offer* b) // expects: a != nullptr && b != nullptr ; If these condition is not met, the values do not represent offers, and function is meaningless
27
Contract violation
28
Contract violation What if a is null?
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } What if a is null?
29
Contract violation What if a is null? Undefined behavior (UB)
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } What if a is null? Undefined behavior (UB)
30
Contract violation What if a is null? Undefined behavior (UB):
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } What if a is null? Undefined behavior (UB): Random values from random memory locations used Program shutdown
31
Contract violation What if a is null? Undefined behavior (UB):
bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } What if a is null? Undefined behavior (UB): Random values from random memory locations used Program shutdown Can help detect bugs (statically)
32
Contract violation inline bool is_better(const Offer* a, const Offer* b) { return a->income() > b->income(); } Offer o; return is_better(&o, nullptr); // static analyzer warning If a tool can see both function definition and the usage it can warn about a bug
33
Contract violation Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // TODO: handle it } if (x) return is_better_offer(x, y); // warning: y could be null
34
Contract violation bool is_better(const Offer* a, const Offer* b) { // expects: a != nullptr && b != nullptr return a->income() > b->income(); } Often, the set of meaningful values is expressible as a C++ predicate.
35
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) REACT(); return a->income() > b->income(); }
36
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) return false; return a->income() > b->income(); }
37
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) return false; return a->income() > b->income(); } Contract artificially widened, with dangerous results: is_better(&x, 0) == false; // &x <= 0 is_better(0 , &y) == false; // <= &y is_better(&x, &y) == true; // &x <= &y
38
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) throw Bug{}; return a->income() > b->income(); }
39
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) throw Bug{}; return a->income() > b->income(); } Offer o; return is_better(&o, nullptr); // no UB -- no warning
40
Contract violation Undesired values are often produced by prior UB:
Dangling pointers Bad usages of memset Data races
41
Contract violation bool is_better(const Offer* a, const Offer* b) { if (!a || !b) std::abort(); return a->income() > b->income(); }
42
Contract violation No further damage.
bool is_better(const Offer* a, const Offer* b) { if (!a || !b) std::abort(); return a->income() > b->income(); } No further damage. Core dump for post-mortem analysis
43
Contract violation Is crashing a good option?
Weight & balance calculator: user can go to manual Word processor: better to waste 1hr of work that 1 day of work Drone: better to restart than do random actions Financial server: better go down than make bad decisions Assisting troops: better go down than give false sense of security
44
Precondition
45
Precondition Precondition: a constraint/predicate on input values
bool is_better(const Offer* a, const Offer* b) { // precondition: a != nullptr // precondition: b != nullptr ; Precondition: a constraint/predicate on input values if not satisfied, the input does not represent the abstraction if not satisfied, the function cannot produce a meaningful value
46
Precondition bool is_better(const Offer* a, const Offer* b) { // precondition: a != nullptr // precondition: b != nullptr ; If precondition is not satisfied, there is a bug before the function call. You know what to fix: the caller.
47
Precondition bool is_better(const Offer* a, const Offer* b) { // precondition: a != nullptr // precondition: b != nullptr ; A precondition helps detect bugs because it defines what a bug is. Language support for contracts: Standardize the notation. No specific “semantics”.
48
Precondition
49
Precondition What if lo > hi?
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } What if lo > hi?
50
Precondition What if lo > hi? No undefined behavior (UB)
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } What if lo > hi? No undefined behavior (UB)
51
Precondition What if lo > hi? No undefined behavior (UB)
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } What if lo > hi? No undefined behavior (UB) “no problem to solve” “some value is produced”
52
Precondition What if lo > hi?
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } What if lo > hi? No “range” anymore: just a combination of operations. The abstraction is weakened.
53
Precondition The potential bug is still there.
bool is_in_range(int x, int lo, int hi) { return lo <= x && x <= hi; } return is_in_range(lo, hi, val); The potential bug is still there.
54
Precondition bool is_in_range(int x, int lo, int hi) { if (lo > hi) std::abort(); return lo <= x && x <= hi; }
55
Precondition Give hint to static analyzer
inline bool is_in_range(int x, int lo, int hi) { if (lo > hi) { *((int*)0) = 0; } // explicit UB return lo <= x && x <= hi; } Give hint to static analyzer Is UB more dangerous than a bug?
56
Precondition inline bool is_in_range(int x, int lo, int hi) { if (lo > hi) TRAP(); return lo <= x && x <= hi; } #if defined STATIC_ANALYSIS # define TRAP() { *((int*)0) = 0; } #else # define TRAP() std::abort() #elif
57
Precondition bool is_in_range(int x, Range r) { return r.lo() <= x && x <= r.hi(); } return is_in_range(val, Range{lo, hi});
58
Precondition bool is_in_range(int x, Range r) { return r.lo() <= x && x <= r.hi(); } return is_in_range(val, Range{lo, hi}); return is_in_range(Range{lo, hi}, val); // type-system error
59
Precondition What if r.lo() > r.hi()?
bool is_in_range(int x, Range r) { return r.lo() <= x && x <= r.hi(); } What if r.lo() > r.hi()?
60
Invariant
61
Invariant class Range { int _lo, _hi; // private public: Range(int l, int h); int lo() const { return _lo; } int hi() const { return _hi; } // invariant: lo() <= hi() };
62
Invariant class Range { int _lo, _hi; // private public: Range(int l, int h); int lo() const { return _lo; } int hi() const { return _hi; } bool invariant() const { return lo() <= hi(); } };
63
Invariant class Range { int _lo, _hi; // private public: Range(int l, int h); int lo() const { assert(invariant()); return _lo; } int hi() const { assert(invariant()); return _hi; } bool invariant() const { return lo() <= hi(); } };
64
Invariant Technically true, but too obvious.
bool is_in_range(int x, Range r) // precondition: r.invariant() ; Technically true, but too obvious. Invariant on parameters is an implied precondition.
65
Invariant bool is_in_range(int x, Range r) // precondition: r.invariant() ; return is_in_range(val, Range{hi, lo}); // still a bug
66
Invariant class Range { int _lo, _hi; public: Range(int l, int h) // precondition: l <= h : _lo(l), _hi(h) {} int lo() const { return _lo; } int hi() const { return _hi; } bool invariant() const { return lo() <= hi(); } };
67
Invariant An invariant is a conditional guarantee
It depends on the preconditions of member functions
68
Invariant is_in_range(val, Range{hi, lo}); // still a bug
69
Invariant is_in_range(lo, hi, val); // no longer a bug
70
Invariant Before After Range r = bounds(); is_in_range(x, r);
auto [a, b] = bounds(); // a > b is_in_range(x, a, b); Range r = bounds(); is_in_range(x, r);
71
Invariant The scope of range-related bugs is narrowed:
Potential bugs whenever you create the Range, No potential bugs when you pass the Range around. Better abstraction encourages fewer bugs.
72
Inexpressible conditions
73
Inexpressible conditions
size_t length(const char* str); What if str is null? What if str does not end with '\0'?
74
Inexpressible conditions
size_t length(const char* str) // precondition: str != nullptr ; What if str is null? What if str does not end with '\0'?
75
Inexpressible conditions
size_t length(const char* str) // precondition: str != nullptr // precondition: ??? ; What if str is null? What if str does not end with '\0'?
76
Inexpressible conditions
size_t length(C_string str);
77
Inexpressible conditions
class C_string { const char * _str; public: // invariant: _str != nullptr && ??? };
78
Inexpressible conditions
C_string str = get_name(); return length(str);
79
Inexpressible conditions
C_string str = get_name(); return length(str); Some APIs will always use const char*.
80
Inexpressible conditions
class C_string { const char * _str; public: explicit C_string(const char * str) // precondition: str != nullptr && ??? ; // invariant: _str != nullptr && ??? };
81
Invariant Run-time checking is out of the question.
Static analysis may still be possible.
82
Inexpressible conditions
inline bool is_null_terminated(const char *) [[symbolic]] // <-- for static analyzers { return true; // <-- for run-time checking }; Run-time checking: avoid false positives. Static analysis: treat it as “symbol”, ignore function body.
83
Inexpressible conditions
size_t length(const char* str) // precondition: str != nullptr // precondition: is_null_terminated(str) ; What if str is null? What if str does not end with '\0'?
84
Inexpressible conditions
const char * name = get_name(); return length(name); // potential contract violation
85
Inexpressible conditions
const char * name = get_name(); return length(name); // proven correct const char * get_name() // postcondition(r): r != nullptr && is_null_terminated(r) ; Introducing name r to refer to the returned value.
86
Postcondition Postconditions are useful for matching against preconditions.
87
Postcondition Postcondition is expected to hold if:
const Offer* better(const Offer* a, const Offer* b) // precondition: a != nullptr // precondition: b != nullptr // postcondition(r): r != nullptr ; Postcondition is expected to hold if: Function does not report a failure (like, throws exception) Function preconditions are satisfied
88
Contracts in the language
89
Contracts in the language
const Offer* better(const Offer* a, const Offer* b) { CHECK_PRECONDITION(a != nullptr); CHECK_PRECONDITION(b != nullptr); // ... } Helps prevent damage Helps testing Does not prevent bugs
90
Contracts in the language
const Offer* better(const Offer* a, const Offer* b) [[pre: a != nullptr]] [[pre: b != nullptr]] // warning: invented syntax [[post r: r != nullptr]] ; C++ predicates associated with function declaration: Compiler type-checks them Static analyzer does not need to see the body
91
Contracts in the language
bool is_in_range(int x, int lo, int hi) [[pre: lo <= hi]] { return lo <= x && x <= hi; } C++ predicates associated with function declaration: Compiler type-checks them Static analyzer does not need to see the body
92
Contracts in the language
bool is_in_range(int x, int lo, int hi) [[pre: lo <= hi]] { return lo <= x && x <= hi; } Static analyzer can detect a bug even if no UB is present.
93
Contracts in the language
bool is_in_range(int x, int lo, int hi) [[pre: lo <= hi]] { // if (!(lo <= hi)) std::abort(); // <- can be injected return lo <= x && x <= hi; } Compiler can inject a code that checks the condition and aborts. This does not compromise static analysis.
94
Contracts in the language
class Range { public: Range(int l, int h) [[pre: l <= h ]]; int lo() const; int hi() const; [[assert: lo() <= hi()]]; }; You can put them in places where C-assertions cannot go. Standard notation recognized by every tool.
95
Contract violation as a symptom
96
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // TODO: handle it } if (x) return is_better_offer(x, y);
97
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // TODO: handle it } if (x) return is_better_offer(x, y); // <- analyzer warning
98
Contract violation as a symptom
Contract violation indicates a bug somewhere before. Analyzer knows that there is some bug. It does not know what or where the bug is. Programmer has to go and find it.
99
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // TODO: handle it } if (x) return is_better_offer(x, y); // <- analyzer warning
100
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // TODO: handle it } if (x && y) return is_better_offer(x, y); // <- no analyzer warning // but bug still presnt
101
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; try { x = compute_offer(); y = compute_offer(); } catch(...) { // <-- real bug is with the premature catch // TODO: handle it } if (x) return is_better_offer(x, y);
102
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; x = compute_offer(); y = compute_offer(); if (x) return is_better_offer(x, y);
103
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; x = compute_offer(); y = compute_offer(); if (x) return is_better_offer(x, y);
104
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; x = compute_offer(); y = compute_offer(); return is_better_offer(x, y);
105
Contract violation as a symptom
Offer *x = nullptr, *y = nullptr; x = compute_offer(); y = compute_offer(); return is_better_offer(x, y);
106
Contract violation as a symptom
Offer *x = compute_offer(); Offer *y = compute_offer(); return is_better_offer(x, y); Bug is fixed. Program is simpler.
107
Summary
108
Summary Contracts define what a bug is in a formal way:
Predicates are C++ expressions type-checked by the compiler If the predicate returns false there is a bug before the statement (or inside the contract predicate) They can appear in function declarations & class definitions
109
Summary The information about the bug can be used in various ways:
Static analysis Code reviews Code generation (defensive if-s) More...
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.