Presentation is loading. Please wait.

Presentation is loading. Please wait.

Preconditions, postconditions, invariants how they help write robust programs Andrzej Krzemieński akrzemi1.wordpress.com.

Similar presentations


Presentation on theme: "Preconditions, postconditions, invariants how they help write robust programs Andrzej Krzemieński akrzemi1.wordpress.com."— Presentation transcript:

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...

110


Download ppt "Preconditions, postconditions, invariants how they help write robust programs Andrzej Krzemieński akrzemi1.wordpress.com."

Similar presentations


Ads by Google