Sign in Join
Wiki copytemporary

Defensive programming techniques: what changed

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

OldNewLine
11## How to catch and prevent Range Errors
22
33Range 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\|
44
55``` pascal
66{$R-}
77```
88
99\]\] in a unit and nobody noticed this for years. When I compiled the code during a review with range checks on
1010
1111``` pascal
1212{$R+}
1313```
1414
1515I 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.
1616
1717I will show you how to find range errors, how to debug them and how to prevent them. Defensive programming is important with ranges.
1818
1919### The bug
2020
2121Let's introduce you to a small piece of code with a range bug.
2222
2323``` pascal
2424program dtp_1a;
2525{$mode objfpc}
2626var
2727 anArray:array[0..9] of integer; // ten elements
2828 i:integer;
2929begin
3030 for i := 1 to 10 do
3131 begin
3232 anArray[i] := i;
3333 write(anArray[i]:3);
3434 end;
3535end.
3636```
3737
3838This code compiles without error and on some systems it even runs! without error:
3939
4040``` bash
4141$ fpc -glh dtp_1a.pas
4242```
4343
4444Note `-glh` obtains line info in case of an error. Running the program yields:
4545
4646```text
4747dtp
4848 1 2 3 4 5 6 7 8 9 10
4949```
5050
5151That may seem right, but is wrong! It could also SEGFAULT or worse … Which you know if you have spotted the bug.
5252
5353### Turn range checks on
5454
5555Now let's see what happens when we compile with range checks:
5656
5757``` pascal
5858program dtp_1b;
5959{$mode objfpc}
6060{$R+}
6161var
6262 anArray:array[0..9] of integer; // ten elements
6363 i:integer;
6464begin
6565 for i := 1 to 10 do
6666 begin
6767 anArray[i] := i;
6868 write(anArray[i]:3);
6969 end;
7070end.
7171```
7272
7373``` bash
7474$ fpc -glh dtp_1b.pas
7575```
7676
7777You 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:
7878
7979```text
8080dtp
8181 1 2 3 4 5 6 7 8 9Runtime error 201 at $000101B8
8282 $000101B8 main, line 10 of dtp.pas
8383 $00010124
8484
8585No heap dump by heaptrc unit
8686Exitcode = 201
8787```
8888
8989Ok, 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?
9090
9191``` pascal
9292i
9393```
9494
9595or
9696
9797``` pascal
9898anArray[i]
9999```
100100
101101or both? And when it goes wrong is also not obvious to all.
102102
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
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 F9 a couple of times. Note I also set a watch on
104104
105105``` pascal
106106i
107107```
108108
109109.
110110
111-<figure>
112-<img src="dtp_1b.png" title="dtp_1b.png" />
113-<figcaption>dtp_1b.png</figcaption>
114-</figure>
111+![](/wiki-images/d/d2/Dtp_1b.png)
115112
116-So I pressed <span class="kbd">F9</span> ten times and hey presto, the error occurs when
113+So I pressed F9 ten times and hey presto, the error occurs when
117114
118115``` pascal
119116i
120117```
121118
122119becomes 10 and we try to access
123120
124121``` pascal
125122anArray[10]
126123```
127124
128125. But that means the actual error is on line 9. We are over-indexing because the array is from
129126
130127``` pascal
1311280..9
132129```
133130
134131, not from
135132
136133``` pascal
1371341
138135```
139136
140137to
141138
142139``` pascal
14314010
144141```
145142
146143.
147144
148145**Bug found and cause of bug found.** But not fixed, remember we found it at runtime, not at compile time.
149146
150147> **Note:** To summarize, turning range checks on finds range errors at run time, but not always at compile time.
151148
152149=== Declare ranges and use
153150
154151``` pascal
155152low()
156153```
157154
158155and
159156
160157``` pascal
161158high()
162159```
163160
164161=== 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.
165162
166163``` pascal
167164program dtp_1c;
168165{$mode objfpc}{$R+}
169166var
170167 anArray: array[0..9] of integer; // ten elements
171168 i: 0..9; // range of 10 elements, same as array
172169begin
173170 for i := 1 to 10 do
174171 begin
175172 anArray[i] := i;
176173 write(anArray[i]:3);
177174 end;
178175end.
179176```
180177
181178By 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:
182179
183-<figure>
184-<img src="dtp_1c.png" title="dtp_1c.png" />
185-<figcaption>dtp_1c.png</figcaption>
186-</figure>
180+![](/wiki-images/4/41/Dtp_1c.png)
187181
188182Does 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.
189183
190184It 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.
191185
192186``` pascal
193187program dtp_1d;
194188{$mode objfpc}{$R+}
195189var
196190 anArray:array[0..9] of integer; // ten elements
197191 // if we change the array size this is automatically also correct.
198192 i: low(anArray)..high(anArray);
199193begin
200194 for i := 0 to 9 do // can't write 10 here...
201195 begin
202196 anArray[i] := i;
203197 write(anArray[i]:3);
204198 end;
205199end.
206200```
207201
208202For completeness you can also use it like this. If any size needs to change, simply change the type:
209203
210204``` pascal
211205program dtp_1e;
212206{$mode objfpc}{$R+}
213207type
214208 TmyRange = 0..9;
215209var
216210 i:TMyRange;
217211 anArray:array[TmyRange] of integer; // ten elements
218212begin
219213 for i := Low(TMyRange) to High(TMyRange) do
220214 begin
221215 anArray[i] := i;
222216 write(anArray[i]:3);
223217 end;
224218end.
225219```
226220
227221> **Note:** To summarize: Declaring a specific range can help you find range errors at compile time. Using
228222>
229223> ``` pascal
230224> low()
231225> ```
232226>
233227> and
234228>
235229> ``` pascal
236230> high()
237231> ```
238232>
239233> can prevent you from making range errors.
240234
241235=== Use
242236
243237``` pascal
244238for
245239```
246240
247241…
248242
249243``` pascal
250244in
251245```
252246
253247…
254248
255249``` pascal
256250do
257251```
258252
259253=== Now, forget all the above. When it is possible, you should use \[\[For\|
260254
261255``` pascal
262256for
263257```
264258
265259\]\] … \[\[In\|
266260
267261``` pascal
268262in
269263```
270264
271265\]\] … \[\[Do\|
272266
273267``` pascal
274268do
275269```
276270
277271\]\]. The Pascal language has
278272
279273``` pascal
280274low()
281275```
282276
283277and
284278
285279``` pascal
286280high()
287281```
288282
289283for 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\|
290284
291285``` pascal
292286for
293287```
294288
295289…
296290
297291``` pascal
298292in
299293```
300294
301295…
302296
303297``` pascal
304298do
305299```
306300
307301\]\]. This syntax will simply iterate over all possible values in a collection of data like an array, but without an explicit index.
308302
309303We can get rid of our bug by preventing it in the first place by removing the index altogether.
310304
311305``` pascal
312306program dtp_1d;
313307{$mode objfpc}{$R+}
314308var
315309 anArray:array[0..9] of integer; // ten elements
316310 i:0..9; // could use j, but this is for clarity.
317311 Item:integer; // Item is an integer here: it is not an index, but a value from the array
318312begin
319313 // data to show what for in do does
320314 for i := Low(anArray) to High(anArray) do anArray[i] := 100+i;
321315 for Item in anArray do // for every integer value that is contained in the array
322316 write(Item:4); // writes the value of an array cell, this is not an index.
323317end.
324318```
325319
326320> **Note:** To summarize: with
327321>
328322> ``` pascal
329323> for
330324> ```
331325>
332326> …
333327>
334328> ``` pascal
335329> in
336330> ```
337331>
338332> …
339333>
340334> ``` pascal
341335> do
342336> ```
343337>
344338> you can safely iterate over a collection of data without using an explicit index and the risk of range errors.
345339
346340### Bonus: Using a range? You may want a set, too!
347341
348342If 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.
349343
350344A simple example looks like this:
351345
352346``` pascal
353347program dtp_1f;
354348{$mode objfpc}{$R+}
355349type
356350 TmyRange = 0..9;
357351var
358352 i:TMyRange;
359353 j:set of TMyRange;
360354 anArray:array[TmyRange] of integer; // ten elements
361355begin
362356 j:=[1,3,5,7,9];// odd elements
363357 for i in j do
364358 begin
365359 anArray[i] := i;
366360 write(anArray[i]:3);
367361 end;
368362end.
369363```
370364
371365Ranges are powerful, sets are even more so! And makes your code safe and readable.
372366
373367### Conclusion
374368
375369Range errors are common in every language, often hard to find, but if you are reading this you are probably using Pascal.
376370
377371And with the right mindset a Pascal programmer can write code in such a way that range errors should hardly exist in the code.
378372
379373Because Pascal is so strongly typed and has so many features to help you prevent range errors.
380374
381375- use \[\[sRangechecks\|
382376 ``` pascal
383377 {$rangechecks on}
384378 ```
385379
386380 \]\] or
387381
388382 ``` pascal
389383 {$R+}
390384 ```
391385
392386 during development and run your code. Turn it off if you are sure there are no range errors but protect your code with ranges.
393387- 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.
394388- use
395389 ``` pascal
396390 low()
397391 ```
398392
399393 and
400394
401395 ``` pascal
402396 high()
403397 ```
404398
405399 not
406400
407401 ``` pascal
408402 1
409403 ```
410404
411405 to
412406
413407 ``` pascal
414408 10
415409 ```
416410
417411 or
418412
419413 ``` pascal
420414 0
421415 ```
422416
423417 to
424418
425419 ``` pascal
426420 9
427421 ```
428422
429423 when you iterate a data collection. Make it a habit.
430424- use
431425 ``` pascal
432426 for
433427 ```
434428
435429 …
436430
437431 ``` pascal
438432 in
439433 ```
440434
441435 …
442436
443437 ``` pascal
444438 do
445439 ```
446440
447441 if applicable, try to make that your first option!
448442- use a *set of range* to safely filter
449443
450444There 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!
451445
452446## How to prevent Overflow Errors, catch them and even misuse Overflow
453447
454448### The bug
455449
456450Let's introduce you to a small piece of code with an overflow bug.
457451
458452``` pascal
459453program dtp_2a;
460454{$mode objfpc}
461455var
462456 a:NativeInt = high(NativeInt);
463457begin
464458 a:= a + 1;
465459 writeln(a);
466460end.
467461```
468462
469463Can you spot the bug? Concentrate, look again… Can you see it?
470464
471465Now compile that like `fpc dtp_2a.pas`. Then run it:
472466
473467``` bash
474468$ ./dtp_2a
475469-2147483648 //depending on nativeint: this is 32 bit
476470```
477471
478472It does not crash, it simply prints `-2147483648`. But is that correct? Of course not! Now with overflowchcks on:
479473
480474``` pascal
481475program dtp_2a;
482476{$mode objfpc}{$overflowchecks on}
483477var
484478 a:NativeInt = high(NativeInt);
485479begin
486480 a:= a + 1;
487481 writeln(a);
488482end.
489483```
490484
491485This code will compile, but it will generate an overflow error when you run it: 215. See the programmers guide on overflow checks.
492486
493487## How to prevent Input and Output Errors (and how to catch them…)
494488
495489## How to use meaningful Assertions
496490
497491## To serve and protect: the story of try..finally
498492
499493## Do you know your String Type? Really?
500494
501495*\[This should be written by Juha… not me…\]*
502496
503497**string** is a devil with many faces: It can be ShortString, AnsiString and UnicodeString.\
504498I 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:
505499
506500``` pascal
507501//{$mode delphi} // tkAString AnsiString
508502//{$mode delphi}{$H-} // tkSString ShortString
509503//{$mode delphiunicode} // tkUString Unicode string
510504//{$mode delphiunicode}{$H-} // tkSString ShortString
511505//{$mode objfpc} // tkSString ShortString
512506//{$mode objfpc}{$H+} //tkAString AnsiString
513507//{$mode fpc}{$modeswitch result} // tkSString ShortString
514508//{$mode fpc}{$H+}{$modeswitch result} // tkAString AnsiString
515509// etc.
516510
517511uses typinfo;
518512
519513 function StringType(const s:string):TTypeKind;inline;
520514 var info:PTypeInfo;
521515 begin
522516 info:=TypeInfo(s);
523517 Result := Info^.Kind;
524518 end;
525519
526520var s: string = 'testme';
527521begin
528522 writeln('My string type is ',StringType(s));
529523end.
530524```
531525
532526**string** depends on mode,and this little gem will tell you what kind of string you are dealing with.\
533527That is not always obvious. Try to experiment with some of the mode settings and see what happens.\
534528The 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.
535529
536530---
537531*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).*
538532Category: Code
539533Category: Tutorials

Back to history