defensive-coding-guide/modules/ROOT/pages/programming-languages/C-Other.adoc
2020-06-08 18:15:11 +10:00

135 lines
4.1 KiB
Text

:experimental:
[[sect-Defensive_Coding-C-Other]]
== Other C-related Topics
[[sect-Defensive_Coding-C-Wrapper-Functions]]
=== Wrapper Functions
Some libraries provide wrappers for standard library functions.
Common cases include allocation functions such as
`xmalloc` which abort the process on
allocation failure (instead of returning a
`NULL` pointer), or alternatives to relatively
recent library additions such as `snprintf`
(along with implementations for systems which lack them).
In general, such wrappers are a bad idea, particularly if they
are not implemented as inline functions or preprocessor macros.
The compiler lacks knowledge of such wrappers outside the
translation unit which defines them, which means that some
optimizations and security checks are not performed. Adding
`__attribute__` annotations to function
declarations can remedy this to some extent, but these
annotations have to be maintained carefully for feature parity
with the standard implementation.
At the minimum, you should apply these attributes:
* If you wrap function which accepts are GCC-recognized format
string (for example, a `printf`-style
function used for logging), you should add a suitable
`format` attribute, as in <<ex-Defensive_Coding-C-String-Functions-format-Attribute>>.
* If you wrap a function which carries a
`warn_unused_result` attribute and you
propagate its return value, your wrapper should be declared
with `warn_unused_result` as well.
* Duplicating the buffer length checks based on the
`__builtin_object_size` GCC builtin is
desirable if the wrapper processes arrays. (This
functionality is used by the
`-D_FORTIFY_SOURCE=2` checks to guard
against static buffer overflows.) However, designing
appropriate interfaces and implementing the checks may not
be entirely straightforward.
For other attributes (such as `malloc`),
careful analysis and comparison with the compiler documentation
is required to check if propagating the attribute is
appropriate. Incorrectly applied attributes can result in
undesired behavioral changes in the compiled code.
[[sect-Defensive_Coding-C-Common-Mistakes]]
=== Common mistakes
==== Mistakes in macros
A macro is a name given to a block of C statements as a pre-processor
directive. Being a pre-processor the block of code is transformed by
the compiler before being compiled.
A macro starts with the preprocessor directive, #define. It can
define a single value or any 'substitution', syntactically valid or
not.
A common mistake when working with macros is that programmers treat
arguments to macros like they would functions. This becomes an issue
when the argument may be expanded multiple times in a macro.
For example:
macro-misuse.c
[source,C]
----
#define simple(thing) do { \
if (thing < 1) { \
y = thing; \
} \
else if (thing > 100) { \
y = thing * 2 + thing; \
} \
else { \
y = 200; \
} \
} while (0)
int main(void) {
int x = 200;
int y = 0;
simple(x++);
return 0;
}
----
Each pass through the simple() macro would mean that x could be
expanded in-place each time 'thing' was mentioned.
The 'main' function would be processed and expanded as follows:
macro-misuse-post-processing.c
[source,C]
----
int main(void) {
int x = 200;
int y = 0;
do {
if ( x++ < 1) {
y = x++;
}
else if (thing > 100) {
y = x++ * 2 + x++;
}
else {
x = 200;
}
} while (0)
return 0;
}
----
Each evaluation of the argument to 'simple' (x++) would be executed
each time it was referenced.
While this may be 'expected' behaviour by the original creator, large
projects may have programmers who were unaware of how the macro may
expand and this may introduce unexpected behaviour, especially if the
value is later used as indexing into an array or able to be
overflowed.