Code

Node tool: special case node duplication for endnodes - select new endnode
[inkscape.git] / src / proofs
1 This file contains some loose proofs of a few properties.  It's somewhat
2 ad-hoc.  At least it gives an indication of what assert/g_assert calls have
3 been checked by a developer.  If an assertion does trigger, then this file may
4 help in debugging that assertion failure.
6 It's currently ordered by caller.
8 (Re-ordering to avoid forward references in proofs might be a good idea,
9 though this would in some cases require splitting up the proofs for a routine,
10 e.g. proving preconditions of g called from f, then proving g's postcondition,
11 then using that to prove something else in f again.  Furthermore it may not
12 even be possible to avoid forward references for recursive/looping code.)
16 src/pencil-context.cpp:fit_and_split
18 Very loose proof of !sp_curve_empty (pc->red_curve) assertion:
19 fit_and_split is called successively with its input varying only by appending a point.
20 For the n_segs > 0 && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) condition to fail,
21 we must have at least 3 distinct points, which means that a previous call had 2 distinct points,
22 in which case we'd have filled in pc->red_curve to a non-empty curve.
24 Expansion of the above claim of at least 3 distinct points: We know n_segs <= 0 ||
25 unsigned(dc->npoints) >= G_N_ELEMENTS(pc->p) from the negation of the containing `if' condition.
26 G_N_ELEMENTS(pc->p) is greater than 3 (in int arithmetic), from SPPencilContext::p array definition
27 in pencil-context.h.  npoints grows by no more than one per fit_and_split invocation; we should be
28 able to establish that dc->npoints == G_N_ELEMENTS(pc->p) if unsigned(dc->npoints) >=
29 G_N_ELEMENTS(pc->p), in which case 3 <= dc->npoints in int arithmetic.  We know that dc->npoints >=
30 2 from assertion at head of fit_and_split; in which case if n_segs <= 0 then fit_and_split has
31 failed, which implies that dc->npoints > 2 (since the fitter can always exactly fit 2 points,
32 i.e. it never fails if npoints == 2; TODO: add sp_bezier_fit_cubic postcondition for this).
35 src/pencil-context.cpp:fit_and_split
37 Proof of precondition: The only caller is spdc_add_freehand_point (by
38 textual search in that file, and staticness).  See proof for that
39 function.
42 src/pencil-context.cpp:spdc_add_freehand_point
44 Proof of fit_and_split `pc->npoints > 1' requirement:
45 It initially unconditionally asserts `pc->npoints > 0'.  There are no function calls or modifications
46 of pc or pc->npoints other than incrementing pc->npoints after that assertion.
47 We assume that integer overflow doesn't occur during that increment,
48 so we get pc->npoints > 1.
51 src/pencil-context.cpp:spdc_set_endpoint
53 Very loose proof of npoints > 0: Should be preceded by spdc_set_startpoint(pc) according to state
54 transitions.  spdc_set_startpoint sets pc->npoints to 0 (handled at beginning of function) or 1.
57 src/display/bezier-utils.cpp:compute_max_error
59 Proof of postcondition: *splitPoint is set only from i, which goes from 1 to less than last.
60 i isn't written to in the loop body: only uses are indexing built-in arrays d and u
61 (and RHS of assignment).
64 src/display/bezier-utils.cpp:sp_bezier_fit_cubic_full
66 Proof of `nsegs1 != 0' assertion: nsegs1 is const.  Have already
67 returned in the (nsegs1 < 0) case, so !(nsegs1 < 0), i.e. nsegs1 >= 0
68 (given that nsegs1 is gint).  nsegs1 is set to
69 sp_bezier_fit_cubic_full(_, _, _, splitPoint + 1, ...).  We will show
70 that sp_bezier_fit_cubic_full ensures len < 2 || ret != 0.  splitPoint
71 > 0 from compute_max_error postcondition combined with error >=
72 precondition and thus having handled the compute_max_error returning 0
73 case: if returned 0 for maxError then maxError <= error * 9.0 would be
74 true, and we recalculate splitPoint; if the renewed maxError is 0 then
75 the maxError <= error test will succeed and we return.  If we don't
76 return, then the most recent compute_max_error must have returned
77 non-zero, which implies (through compute_max_error postcondition) that
78 splitPoint would have been set s.t. 0 < splitPoint.  splitPoint is not
79 subsequently written to.  (It is subsequently passed only to
80 sp_darray_center_tangent 2nd arg, which is a plain unsigned rather
81 than reference.)  0 < splitPoint < last guarantees splitPoint + 1 >=
82 2.  (We know splitPoint + 1 won't overflow because last = len - 1 and
83 len is of the same type as splitPoint.)  Passing splitPoint + 1 for
84 len of the call that sets nsegs1 ensures that nsegs1 is non-zero (from
85 the len < 2 || ret != 0 property that we show below).  QED.
87 Proof that len < 2 || (failed no-dups precondition) || ret != 0: All
88 returns are either -1, 0, 1, or nsegs1 + nsegs2.  There are two
89 literal 0 cases: one conditional on len < 2, and the other for failed
90 precondition (non-uniqued data).  For the nsegs1 + nsegs2 case, we've
91 already ruled out nsegs1 < 0 (through conditional return) and nsegs2 <
92 0 (same).  The nsegs1 + nsegs2 case occurs only when we recurse; we've
93 already shown the desired property for non-recursive case.  In the
94 nsegs1 non-recursive case, we have that nsegs1 != 0, which combined
95 with !(nsegs1 < 0) and !(nsegs2 < 0) implies that nsegs1 + nsegs2
96 either overflows or is greater than 0.  We should be able to show that
97 nsegs1 + nsegs2 < len even with exact arithmetic.  (Very loose proof:
98 given that len >= 2 (from earlier conditional return), we can fit len
99 points using len-1 segments even using straight line segments.)
100 nsegs1 and nsegs2 are the same type as len, and we've shown that
101 nsegs1 + nsegs2 in exact arithmetic is >= 0 from each operand being
102 non-negative, so nsegs1 + nsegs2 doesn't overflow.  Thus nsegs1 +
103 nsegs2 > 0.  Thus we have shown for each return point that either the
104 return value is -1 or > 0 or occurs when len < 2 or in failure of
105 no-dups precondition.  (We should also show that no-dups outer
106 precondition being met implies it being met for inner instances of
107 sp_bezier_fit_cubic_full, because we pass a subsequence of the data
108 array, and don't write to that array.)  QED.
110 We should also show that the recursion is finite for the inductive
111 proof to hold.  The finiteness comes from inner calls having len > 0
112 and len less than that of outer calls (from splitPoint calculation and
113 0 < splitPoint < last for recursive case and last < len and transitive
114 property of < for gint).  If len < 2 then we don't recurse
115 (conditional return).
117 We should go over this proof to make it clear that there are no
118 "forward references" other than for recursive case.  We could also be
119 more formal in use of inductive proof (e.g. clearly stating what the
120 base and inductive premises are; namely the non-recursing and
121 recursing cases of sp_bezier_fit_cubic_full).
123 Better proof sketch that nseg1 + nsegs2 < len: ret < len for each
124 recursive case other than where len > 0 precondition fails.  nsegs1 is
125 calculated for inner len=splitPoint + 1, nsegs2 for inner len=len -
126 splitPoint.  Assuming exact arithmetic we'll transform that to ret <=
127 len - 1.  Implies that in exact arithmetic, nsegs1 + nsegs2 <=
128 (splitPoint + 1 - 1) + (len - splitPoint - 1).  Simplifying RHS (using
129 exact arithmetic): nsegs1 + nsegs2 <= len - 1, i.e. nsegs1 + nsegs2 <
130 len.  Presumably we can show that machine arithmetic gets the same
131 results as exact arithmetic from similar arguments used so far for
132 showing that overflow doesn't occur.  For the recursive case the
133 return values are either nsegs1 + nsegs2 or -1.
135 We should also show that inner preconditions hold, especially the len
136 > 0 precondition.  (For nsegs1 call, we use 0 < splitPoint and that
137 splitPoint + 1 doesn't overflow.  For nsegs2 call, we pass len -
138 splitPoint; combine with splitPoint < last, last = len - 1, and no
139 overflow.)  We've already sketched a proof for no-dups precondition.
140 The others are fairly simple.
142 For error >= 0: error is const, and we pass it to all recursions.
144 For inner max_beziers >= 1: recursions are conditional on outer
145 1 < max_beziers before setting rec_max_beziers1 to max_beziers - 1,
146 and passing rec_max_beziers1 as inner max_beziers value,
147 so we have outer max_beziers >= 2 so inner max_beziers >= 1.
148 max_beziers and rec_max_beziers1 are both const.
151 src/display/bezier-utils.cpp:sp_darray_right_tangent(Point const[], unsigned)
153 Proof of unit_vector precondition that a != Point(0, 0): our unequal precondition.
155 Loose (incorrect) proof of unit_vector precondition that neither
156 coordinate is NaN: our in_svg_plane precondition, and fact that
157 in_svg_plane returns false if either argument is infinite.  HOWEVER,
158 the unchecked in_svg_plane precondition isn't currently guaranteed, so
159 we're just relying on the input points never being infinity (which
160 might occur with strange zoom settings).
163 src/display/bezier-utils.cpp:sp_darray_right_tangent(Point const[], unsigned, double)
165 Loose proof of unit_vector precondition that a != Point(0, 0) for first call to unit_vector:
167 We've asserted that 0 <= tolerance_sq; combine with tolerance_sq <
168 distsq and transitivity of <=/< show that 0 < distsq.  Definition of
169 dot should give us that t != 0.0, given that 0.0 * 0.0 == +0.0, and 0
170 < +0.0 is false.
172 Loose proof for the second unit_vector invocation: distsq != 0 from ?:
173 condition, which should give us that t != Point(0, 0) in the same way
174 as in the above proof.
176 Proof of sp_darray_right_tangent(Point[], unsigned) preconditions: We
177 have the same preconditions, and pass the same arguments.  d, *d and
178 len are const.
182 src/extension/internal/ps.cpp:PrintPS::print_fill_style:
184 Proof of the
185    g_return_if_fail( style->fill.type == SP_PAINT_TYPE_COLOR
186                      || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER
187                           && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) )
188 at beginning of function:
190 rgrep print_fill_style reveals no callers in any other files.  There are two calls in ps.cpp, both
191 inside an `if' test of that same condition (with no relevant lines between the test and the call).
192 Each call uses `style' as its second argument, and `style' in print_fill_style refers to its second
193 parameter.  In both caller & callee, `style' is a const pointer to const, and there is very little
194 code between the two tests, so the relevant values are very unlikely to change between the two
195 tests.
198 Proof of
199     g_assert( style->fill.type == SP_PAINT_TYPE_PAINTSERVER
200               && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) :
202 The g_return_if_fail(style->fill.type == SP_PAINT_TYPE_COLOR
203                      || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER
204                           && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) )
205 call at the beginning of the function, and we're in the `else' branch of a test for
206 style->fill.type == SP_PAINT_TYPE_COLOR, and style is a const pointer to const, so it's likely that
207 style->fill and the gradient object have the same values throughout.
211 src/extensions/internal/ps.cpp:PrintPS::fill:
213 Proof of the two assertions
214   g_assert( style->fill.type == SP_PAINT_TYPE_PAINTSERVER
215             && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) ) :
217 Each is in the `else' branch of a test for `style->fill.type == SP_PAINT_TYPE_COLOR',
218 within a test for
219   ( style->fill.type == SP_PAINT_TYPE_COLOR
220     || ( style->fill.type == SP_PAINT_TYPE_PAINTSERVER
221          && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) )  ).
223 `style' is a const pointer to const, so the values are unlikely to have changed between the tests.
227 src/seltrans.cpp:sp_sel_trans_update_handles
229 Proof of requirements of sp_show_handles:
231 sp_show_handles requirements: !arg1.empty.
233 Before any call to sp_show_handles is a test `if (... || seltrans.empty) { ...; return; }'
234 (with no `break' etc. call preventing that `return').
235 Each subsequent sp_show_handles call uses seltrans as arg1.
236 seltrans is a reference.  There are no calls between that failing seltrans.empty test
237 and the sp_show_handles calls that pass seltrans.  The sole call is sp_remove_handles,
238 probably doesn't have access to seltrans.
242 src/seltrans.cpp:sp_show_handles
244 Proof of precondition:
246 sp_show_handles is static.  Searching reveals calls only in sp_sel_trans_update_handles (proof above).
250 src/sp-spiral.cpp:sp_spiral_fit_and_draw
252 Proof of postcondition is_unit_vector(*hat2):
254 hat2 is set by sp_spiral_get_tangent unconditionally, which Ensures is_unit_vector(*hat2).
255 We then negate *hat2, which doesn't affect its length.
256 We pass it only to sp_bezier_fit_cubic_full, which claims constness of *hat2.
258 Proof of unconditionalness: Not inside if/for/while.  No previous `return'.
261 src/sp-spiral.cpp:sp_spiral_set_shape
263 Loose proof of requirements for sp_spiral_fit_and_draw:
265  Proof of dstep > 0:
266  
267   SAMPLE_STEP equals .25.
268   spiral->revo is bounded to [0.05, 20.0] (and non-NaN) by various CLAMP calls.
269   (TODO: Add precondition, given that those CLAMP calls are outside of this function.)
270   SAMPLE_SIZE equals 8.
271   dstep is const and equals SAMPLE_STEP / spiral->revo / (SAMPLE_SIZE - 1),
272   == 1 / (4 * [.05, 20.0] * 7)
273   == 1 / [1.4, 560]
274   dstep in [.0018, .714].
276  Proof of is_unit_vector(hat1):
278   Initially guaranteed by sp_spiral_get_tangent Ensures.
279   For subsequent calls, hat1 is set from negated hat2 as set by sp_spiral_fit_and_draw,
280   which Ensures is_unit_vector(hat2).
284 src/style.cpp:sp_css_attr_from_style:
286 Proof of sp_style_write_string pre `style != NULL':
288 Passes style as style argument.  style is const, and has already been checked against NULL.
291 src/style.cpp:sp_css_attr_from_object
293 Proof of `flags in {IFSET, ALWAYS} precondition:
295  $ grep sp_css_attr_from_object `sed 's,#.*,,' make.files `
296  file.cpp:  SPCSSAttr *style = sp_css_attr_from_object (SP_DOCUMENT_ROOT (doc));
297  selection-chemistry.cpp:  SPCSSAttr *css = sp_css_attr_from_object (SP_OBJECT(item), SP_STYLE_FLAG_ALWAYS);
298  selection-chemistry.cpp:  SPCSSAttr *temp = sp_css_attr_from_object (last_element, SP_STYLE_FLAG_IFSET);
299  style.cpp:sp_css_attr_from_object (SPObject *object, guint flags)
300  style.h:SPCSSAttr *sp_css_attr_from_object(SPObject *object, guint flags = SP_STYLE_FLAG_IFSET);
303 src/style.cpp:sp_css_attr_from_style
305 Proof of precondition `style != NULL':
307 Callers are selection-chemistry.cpp and style.cpp:
309  $ grep sp_css_attr_from_style `sed 's,#.*,,' make.files `
310 selection-chemistry.cpp:  SPCSSAttr *css = sp_css_attr_from_style (query, SP_STYLE_FLAG_ALWAYS);
311  style.cpp:sp_css_attr_from_style (SPStyle const *const style, guint flags)
312  style.cpp:  return sp_css_attr_from_style (style, flags);
313  style.h:SPCSSAttr *sp_css_attr_from_style (SPStyle const *const style, guint flags);
315 selection-chemistry.cpp caller: query is initialized from sp_style_new()
316 (which guarantees non-NULL), and is const.
318 style.cpp caller: preceded by explicit test for NULL:
320  $ grep -B2 sp_css_attr_from_style style.cpp|tail -3
321      if (style == NULL)
322          return NULL;
323      return sp_css_attr_from_style (style, flags);
328 # Local Variables:
329 # mode:indented-text
330 # fill-column:99
331 # End:
332 # vim: filetype=text:tabstop=8:encoding=utf-8:textwidth=99 :