TrustPeer Coding Guidelines
This document is meant for all TrustPeer developers. It describes simple
rules that every coder should follow (most importants first):
1. each public item (function/variable/constant/macro) that can be used
by other developpers should be be declared in a declaration-only file (.h
file for C language) with comments that describe the exact contract between
the user and the coder:
- The preconditions are the conditions that the user should respect (coherency of call parameters, value ranges, etc.)
- The postconditions are the ones that the coder should respect (no side
effects except the ones documented, exact description of the results of a
function call, exact range of the return values, error values, etc.)
- in addition to "normal" error return values, the "programming error" ERRPROG return value can be used (by pre/invariant/post assertions) (#define ERRPROG 1), see §2.
- a null return value (0,NULL) must be returned when no error happened (yes, it means return values are used only for errors, just like the Unix processes do)
- The above rules also apply for each non-trivial private item
2. each non-trivial function/macro code should contain assertions returning the ERRPROG value, using one of our macros (an "errprog.h" macro works like g_return_val_if_fail(expr,val) )
- Assertions that try to check all the preconditions of the .h
- Assertions that try to check all the postconditions of the .h
- Assertions that try to check all the internal conditions of the algorithm used (each
non-trivial block or loop should have Invariant assertions)
- Gnu Nana provides predicate logic math tools to check math conditions, but only gcc supports them
- Assertions and ERRPROG value should not be used for "normal" error values that are part of a contract. Use plain return values instead.
3. each contract should be tested by a regression test:
- For each contract, a test function/macro should be written
that will check for most possible input values if the postconditions are
respected at the end (including error values)
- This test will also try to trigger the preconditions assertions for most impossible input values to see if they are detected
-All the regression tests should be called by a cron job. They should be called too when a bug can not be found easily
4. each caller should take error values into account:
-Each time a function/macro is called, the first thing to do
will always be to check for a (non null) return error value and
act accordingly (most of the times it means returning a non null error value to the
caller's caller).
-If no error can ever be returned by a call then a comment should indicate
this besides the call (used mostly when the function called has a void return)
-You should use "Exceptions that must be caught" when your langage supports
them (Java). In other languages, you should always use the return value of
a function to return errors. You should never use void as a return value
(except for proved simple functions named v_yournamehere() ).
-In languages like C, if you don't check a call for a (non null) return value, it simply means your code is flawed.
5. when using a language with pointers/unchecked array indices, you should
assert (or return an error value) before each instruction that uses the content
they point to. :
-Macros can be written to facilitate these checks
-Make sure there is a documented way to find where the array ends: ANSI strings (char arrays) use a null element, i.e. the number 0
-When using raw data from the outside (keyboard/network/files...) you should never use directly functions that can trigger overflows (like ANSI scanf and string.h functions)
-In addition, you should use memory usage testing libraries like Valgrind
6. you should send version numbers in the beginning of each protocol or when writing persistent datas
-When writing a persistent data structure (file, database, ...) you should always begin by the version number of this structure, and all the compatible version numbers. (it's very useful when a given version of the program wants to read data written by another version)
-In the same way, each protocol between processes A and B should start with A giving the version # of the protocol it wants to use, and B replying OK or an earlier version #. Then A reply OK or an earlier version #, and so on till OK or Error if no common version # can be found.
7. you should never use global variables
- they can be modified by every function without knowing which has a bug
- they are not threadsafe
8. when using private functions/variables/constants, you must specify to the compiler that they are private
-The C language uses the keyword "static" at the beginning of
the declaration of such private functions/variables/constants that are not inside a
function.
-Obviously, such private items should never be part of a .h file !
-You should never use "static" inside a function since it is not threadsafe
-If you forget to use "static" when building libraries, then several libraries
can have the same name for their private items, producing problems...
9. when writing a function declaration, you should use "const" for each read-only input parameter
-This is also useful for plain variables (non pointers) since some compilers will issue warnings when you try to use them as return values
10. you should use new blocks {} each time you need new resources or temporary variables
- at the end of each block, make sure you release (free) all resources no more needed: you can add comments for each resource
- add a comment after each closing } /* comment */ to know which { it is associated with
(THINGS TO ADD TO THIS PAGE: {} matching, examples for each rule, ...)