Sign in Join
Wiki copytemporary

Defensive programming techniques: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Defensive programming techniques

OldNewLine
1+## How to catch and prevent Range Errors
2+
3+Range errors are easy to introduce and sometimes hard to find. They can exist for years without being noticed. I have seen production units where range checks were deliberately turned off by adding \[\[sRangechecks\|
4+
5+``` pascal
6+{$R-}
7+```
8+
9+\]\] in a unit and nobody noticed this for years. When I compiled the code during a review with range checks on
10+
11+``` pascal
12+{$R+}
13+```
14+
15+I found a huge bug that potentially could crash a vital piece of software. Mind you, there can be reasons to turn range checks off but never for a whole unit or a whole program, unless it is fully tested for a release.
16+
17+I will show you how to find range errors, how to debug them and how to prevent them. Defensive programming is important with ranges.
18+
19+### The bug
20+
21+Let's introduce you to a small piece of code with a range bug.
22+
23+``` pascal
24+program dtp_1a;
25+{$mode objfpc}
26+var
27+ anArray:array[0..9] of integer; // ten elements
28+ i:integer;
29+begin
30+ for i := 1 to 10 do
31+ begin
32+ anArray[i] := i;
33+ write(anArray[i]:3);
34+ end;
35+end.
36+```
37+
38+This code compiles without error and on some systems it even runs! without error:
39+
40+``` bash
41+$ fpc -glh dtp_1a.pas
42+```
43+
44+Note `-glh` obtains line info in case of an error. Running the program yields:
45+
46+```text
47+dtp
48+ 1 2 3 4 5 6 7 8 9 10
49+```
50+
51+That may seem right, but is wrong! It could also SEGFAULT or worse … Which you know if you have spotted the bug.
52+
53+### Turn range checks on
54+
55+Now let's see what happens when we compile with range checks:
56+
57+``` pascal
58+program dtp_1b;
59+{$mode objfpc}
60+{$R+}
61+var
62+ anArray:array[0..9] of integer; // ten elements
63+ i:integer;
64+begin
65+ for i := 1 to 10 do
66+ begin
67+ anArray[i] := i;
68+ write(anArray[i]:3);
69+ end;
70+end.
71+```
72+
73+``` bash
74+$ fpc -glh dtp_1b.pas
75+```
76+
77+You may not expect this code to compile if you discovered the error, but unfortunately it compiles without error or warning. The fun starts when you run it:
78+
79+```text
80+dtp
81+ 1 2 3 4 5 6 7 8 9Runtime error 201 at $000101B8
82+ $000101B8 main, line 10 of dtp.pas
83+ $00010124
84+
85+No heap dump by heaptrc unit
86+Exitcode = 201
87+```
88+
89+Ok, we found a bug at line 10 of our program and 201 means range error. Useful, but not very, since we had to run the program to make it crash. Hardly acceptable. Furthermore not every programmer sees what the bug is since it occurs in a loop. Which is wrong?
90+
91+``` pascal
92+i
93+```
94+
95+or
96+
97+``` pascal
98+anArray[i]
99+```
100+
101+or both? And when it goes wrong is also not obvious to all.
102+
103+Both the FP [[Textmode IDE|textmode IDE]] and [[Lazarus]] are able to debug our program, so we set a breakpoint on line 10 and press <span class="kbd">F9</span> a couple of times. Note I also set a watch on
104+
105+``` pascal
106+i
107+```
108+
109+.
110+
111+<figure>
112+<img src="dtp_1b.png" title="dtp_1b.png" />
113+<figcaption>dtp_1b.png</figcaption>
114+</figure>
115+
116+So I pressed <span class="kbd">F9</span> ten times and hey presto, the error occurs when
117+
118+``` pascal
119+i
120+```
121+
122+becomes 10 and we try to access
123+
124+``` pascal
125+anArray[10]
126+```
127+
128+. But that means the actual error is on line 9. We are over-indexing because the array is from
129+
130+``` pascal
131+0..9
132+```
133+
134+, not from
135+
136+``` pascal
137+1
138+```
139+
140+to
141+
142+``` pascal
143+10
144+```
145+
146+.
147+
148+**Bug found and cause of bug found.** But not fixed, remember we found it at runtime, not at compile time.
149+
150+> **Note:** To summarize, turning range checks on finds range errors at run time, but not always at compile time.
151+
152+=== Declare ranges and use
153+
154+``` pascal
155+low()
156+```
157+
158+and
159+
160+``` pascal
161+high()
162+```
163+
164+=== Object Pascal has a nice feature that is a bit underused, but is very useful in our case, **ranges**. Basically, by declaring a range we can find range errors at compile time and that is exactly what we want.
165+
166+``` pascal
167+program dtp_1c;
168+{$mode objfpc}{$R+}
169+var
170+ anArray: array[0..9] of integer; // ten elements
171+ i: 0..9; // range of 10 elements, same as array
172+begin
173+ for i := 1 to 10 do
174+ begin
175+ anArray[i] := i;
176+ write(anArray[i]:3);
177+ end;
178+end.
179+```
180+
181+By declaring a range instead of an integer we probably also immediately see the discrepancy in the for to code, but that is not always the case, so let's try to compile the code:
182+
183+<figure>
184+<img src="dtp_1c.png" title="dtp_1c.png" />
185+<figcaption>dtp_1c.png</figcaption>
186+</figure>
187+
188+Does not work, as you can see. The code will not compile because we protected our index variable by applying a range to it. And that is exactly what we want, code that contains bugs should not compile.
189+
190+It is a bit difficult to maintain such code, since we have to keep the array and the range in sync, but that is easy to fix with code like this: Note I also fixed the bug here, because we found the bug and a proper debugging message that the range was wrong.
191+
192+``` pascal
193+program dtp_1d;
194+{$mode objfpc}{$R+}
195+var
196+ anArray:array[0..9] of integer; // ten elements
197+ // if we change the array size this is automatically also correct.
198+ i: low(anArray)..high(anArray);
199+begin
200+ for i := 0 to 9 do // can't write 10 here...
201+ begin
202+ anArray[i] := i;
203+ write(anArray[i]:3);
204+ end;
205+end.
206+```
207+
208+For completeness you can also use it like this. If any size needs to change, simply change the type:
209+
210+``` pascal
211+program dtp_1e;
212+{$mode objfpc}{$R+}
213+type
214+ TmyRange = 0..9;
215+var
216+ i:TMyRange;
217+ anArray:array[TmyRange] of integer; // ten elements
218+begin
219+ for i := Low(TMyRange) to High(TMyRange) do
220+ begin
221+ anArray[i] := i;
222+ write(anArray[i]:3);
223+ end;
224+end.
225+```
226+
227+> **Note:** To summarize: Declaring a specific range can help you find range errors at compile time. Using
228+>
229+> ``` pascal
230+> low()
231+> ```
232+>
233+> and
234+>
235+> ``` pascal
236+> high()
237+> ```
238+>
239+> can prevent you from making range errors.
240+
241+=== Use
242+
243+``` pascal
244+for
245+```
246+
247+…
248+
249+``` pascal
250+in
251+```
252+
253+…
254+
255+``` pascal
256+do
257+```
258+
259+=== Now, forget all the above. When it is possible, you should use \[\[For\|
260+
261+``` pascal
262+for
263+```
264+
265+\]\] … \[\[In\|
266+
267+``` pascal
268+in
269+```
270+
271+\]\] … \[\[Do\|
272+
273+``` pascal
274+do
275+```
276+
277+\]\]. The Pascal language has
278+
279+``` pascal
280+low()
281+```
282+
283+and
284+
285+``` pascal
286+high()
287+```
288+
289+for many years and as shown above it can prevent you from introducing range errors. Modern Pascal has a new similar construct but with a new syntax: \[\[for-in loop\|
290+
291+``` pascal
292+for
293+```
294+
295+…
296+
297+``` pascal
298+in
299+```
300+
301+…
302+
303+``` pascal
304+do
305+```
306+
307+\]\]. This syntax will simply iterate over all possible values in a collection of data like an array, but without an explicit index.
308+
309+We can get rid of our bug by preventing it in the first place by removing the index altogether.
310+
311+``` pascal
312+program dtp_1d;
313+{$mode objfpc}{$R+}
314+var
315+ anArray:array[0..9] of integer; // ten elements
316+ i:0..9; // could use j, but this is for clarity.
317+ Item:integer; // Item is an integer here: it is not an index, but a value from the array
318+begin
319+ // data to show what for in do does
320+ for i := Low(anArray) to High(anArray) do anArray[i] := 100+i;
321+ for Item in anArray do // for every integer value that is contained in the array
322+ write(Item:4); // writes the value of an array cell, this is not an index.
323+end.
324+```
325+
326+> **Note:** To summarize: with
327+>
328+> ``` pascal
329+> for
330+> ```
331+>
332+> …
333+>
334+> ``` pascal
335+> in
336+> ```
337+>
338+> …
339+>
340+> ``` pascal
341+> do
342+> ```
343+>
344+> you can safely iterate over a collection of data without using an explicit index and the risk of range errors.
345+
346+### Bonus: Using a range? You may want a set, too!
347+
348+If you have declared a range, why not declare a [[Set|set]] as well? This will give you a safe way of performing filters on a data collection like an array.
349+
350+A simple example looks like this:
351+
352+``` pascal
353+program dtp_1f;
354+{$mode objfpc}{$R+}
355+type
356+ TmyRange = 0..9;
357+var
358+ i:TMyRange;
359+ j:set of TMyRange;
360+ anArray:array[TmyRange] of integer; // ten elements
361+begin
362+ j:=[1,3,5,7,9];// odd elements
363+ for i in j do
364+ begin
365+ anArray[i] := i;
366+ write(anArray[i]:3);
367+ end;
368+end.
369+```
370+
371+Ranges are powerful, sets are even more so! And makes your code safe and readable.
372+
373+### Conclusion
374+
375+Range errors are common in every language, often hard to find, but if you are reading this you are probably using Pascal.
376+
377+And with the right mindset a Pascal programmer can write code in such a way that range errors should hardly exist in the code.
378+
379+Because Pascal is so strongly typed and has so many features to help you prevent range errors.
380+
381+- use \[\[sRangechecks\|
382+ ``` pascal
383+ {$rangechecks on}
384+ ```
385+
386+ \]\] or
387+
388+ ``` pascal
389+ {$R+}
390+ ```
391+
392+ during development and run your code. Turn it off if you are sure there are no range errors but protect your code with ranges.
393+- use *ranges* instead of integers for your index and think about range when writing your code! It will prevent you from introducing range errors and you will catch them at compile time.
394+- use
395+ ``` pascal
396+ low()
397+ ```
398+
399+ and
400+
401+ ``` pascal
402+ high()
403+ ```
404+
405+ not
406+
407+ ``` pascal
408+ 1
409+ ```
410+
411+ to
412+
413+ ``` pascal
414+ 10
415+ ```
416+
417+ or
418+
419+ ``` pascal
420+ 0
421+ ```
422+
423+ to
424+
425+ ``` pascal
426+ 9
427+ ```
428+
429+ when you iterate a data collection. Make it a habit.
430+- use
431+ ``` pascal
432+ for
433+ ```
434+
435+ …
436+
437+ ``` pascal
438+ in
439+ ```
440+
441+ …
442+
443+ ``` pascal
444+ do
445+ ```
446+
447+ if applicable, try to make that your first option!
448+- use a *set of range* to safely filter
449+
450+There is more to this subject, but if you follow these simple rules you avoid bugs and trust me: there is no speed penalty. A bit of “brains instead of fingers” will prevent this nasty category of bugs and prevents you from spending more debug time than coding time!
451+
452+## How to prevent Overflow Errors, catch them and even misuse Overflow
453+
454+### The bug
455+
456+Let's introduce you to a small piece of code with an overflow bug.
457+
458+``` pascal
459+program dtp_2a;
460+{$mode objfpc}
461+var
462+ a:NativeInt = high(NativeInt);
463+begin
464+ a:= a + 1;
465+ writeln(a);
466+end.
467+```
468+
469+Can you spot the bug? Concentrate, look again… Can you see it?
470+
471+Now compile that like `fpc dtp_2a.pas`. Then run it:
472+
473+``` bash
474+$ ./dtp_2a
475+-2147483648 //depending on nativeint: this is 32 bit
476+```
477+
478+It does not crash, it simply prints `-2147483648`. But is that correct? Of course not! Now with overflowchcks on:
479+
480+``` pascal
481+program dtp_2a;
482+{$mode objfpc}{$overflowchecks on}
483+var
484+ a:NativeInt = high(NativeInt);
485+begin
486+ a:= a + 1;
487+ writeln(a);
488+end.
489+```
490+
491+This code will compile, but it will generate an overflow error when you run it: 215. See the programmers guide on overflow checks.
492+
493+## How to prevent Input and Output Errors (and how to catch them…)
494+
495+## How to use meaningful Assertions
496+
497+## To serve and protect: the story of try..finally
498+
499+## Do you know your String Type? Really?
500+
501+*\[This should be written by Juha… not me…\]*
502+
503+**string** is a devil with many faces: It can be ShortString, AnsiString and UnicodeString.\
504+I have the habit to declare the exact species of string I am using, especially in library code, but what if the code just says **string**? Well, here's a little utility function to obtain the string type you are actually working with:
505+
506+``` pascal
507+//{$mode delphi} // tkAString AnsiString
508+//{$mode delphi}{$H-} // tkSString ShortString
509+//{$mode delphiunicode} // tkUString Unicode string
510+//{$mode delphiunicode}{$H-} // tkSString ShortString
511+//{$mode objfpc} // tkSString ShortString
512+//{$mode objfpc}{$H+} //tkAString AnsiString
513+//{$mode fpc}{$modeswitch result} // tkSString ShortString
514+//{$mode fpc}{$H+}{$modeswitch result} // tkAString AnsiString
515+// etc.
516+
517+uses typinfo;
518+
519+ function StringType(const s:string):TTypeKind;inline;
520+ var info:PTypeInfo;
521+ begin
522+ info:=TypeInfo(s);
523+ Result := Info^.Kind;
524+ end;
525+
526+var s: string = 'testme';
527+begin
528+ writeln('My string type is ',StringType(s));
529+end.
530+```
531+
532+**string** depends on mode,and this little gem will tell you what kind of string you are dealing with.\
533+That is not always obvious. Try to experiment with some of the mode settings and see what happens.\
534+The result may not always be what you expected, so use this function as a debug utility. You can be sure it returns what **string** means at any given unit.
535+
536+---
537+*From the Free Pascal wiki, page "Defensive programming techniques", as it stood on 2021-09-29 after a change by Thaddy, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
538+Category: Code
539+Category: Tutorials

Back to history