You can jump to the “Proposal and RFC” section below if it’s too long to read
the motivation.
This list is usually used for non-technical decisions but I think it would be
great to discuss an issue we have right now and a proposal on how to improve
it.
Currently coverity and other static analysis tools are tricked by the fact we
don’t initialize class members. We rely on either the object being on a BSS
section (and thus zeroed by the compiler/OS) or being allocated with new
operator (we have this file in place for that:
https://github.com/ArduPilot/ardupilot/blob/master/libraries/AP_Common/c++.cpp
which globally replaces the new operator).
I always knew about this “issue”, tried to fix it some times by initializing
the members but this increases the binary size which is particularly bad for
PX4 on old pixhawk1 boards that are almost out of flash space.
However one problem that I hadn’t run into yet is that recent versions of valgrind is
hit by the problem of globally replacing the new operator. I’m explicitly saying
replacing rather than overriding since this is different from c++ override
mechanism. Valgrind hits this problem because it needs to replace the new operator
in order to track memory allocations… just like it does with malloc/calloc and other
functions. The difference here is that malloc/calloc are on libc and it can
simply wrap the call rather than replace. For the new operator, since it’s
embedded in the binary it can’t. This was my poor diagnostic of the situation
with a quick look on valgrind’s source code. You can check the tons of
"use of uninitialized memory" if you use valgrind > 3.11.
Besides being a problem for static/dynamic analysis tools it’s also a problem of
managing the project: there’s currently no way to forbid objects from being
created on stack. With the policy that “we don’t initialize members to zero in
order not to increase the flash size” this can create subtle and difficult to
track bugs. One of the reasons they are difficult to track is because the tools
that report their misuse were rendered useless or difficult due to this policy.
I’ve reached to coverity support once to try to overcome this using their
modelling tools and the feedback I obtained was:
"This type of UNINIT_CTOR defect cannot be suppressed by modelling. They will
have to either use Annotations in their code or explore some of the options
of UNINIT_CTOR to see if something there might help."
So I lost hope on that happening.
PROPOSAL and RFC
Attached 2 draft .cpp files. The requirements I had were:
- Do not increase binary size
- Allow to have statically allocated objects
- Allow to have dynamically allocated objects
- Check if valgrind doesn’t explode
- Make the “we do not zero objects” an opt-in thing: if
we don’t explicitely provide a mechanism in the class definition/declaration
people should use the initialization methods: this forces people to think if
the object can be allocated in the stack
static-allocation.cpp
This would be something to change on AP_InertialSensor, AP_Baro, or any other object
that is made static in AP_HAL_xxxx/AP_HAL_Class.cpp.
The #define NEW 1
is there just to be able to quickly change from the current
implementation to new one.
Bonus point here is to forbid copy constructions, which we have in some of our classes.
As for size:
$ arm-none-eabi-g++ -DNEW=0 -std=gnu++11 -c -Os -o static-allocation.old.o static-allocation.cpp
$ arm-none-eabi-g++ -DNEW=1 -std=gnu++11 -c -Os -o static-allocation.new.o static-allocation.cpp
$ arm-none-eabi-size static-allocation.*.o
text data bss dec hex filename
124 4 16 144 90 static-allocation.new.o
124 4 16 144 90 static-allocation.old.o
test-new-override.cpp
Instead of defining a global new operation we let those objects that are supposed
to be dynamically allocated to override the new operator. I created a base class
"CallocAllocator" that can be inherited, but there are multiple ways to achieve
the same.
This actually reduces the size. I didn’t investigate yet why and I think in the end
of a complete conversion on the codebase the size will actually fluctuate a little bit
up or down.
$ size test-new-overide.*.o
text data bss dec hex filename
149 0 0 149 95 test-new-overide.new.o
165 0 0 165 a5 test-new-overide.old.o
Conclusion
As of now I’m not seeing any drawbacks except the non-trivial amount of manual work
to do the conversion. In the end I think it will be much better though and I am pretty
confident coverity will also be happy with the changes, allowing us to make better use
of it.
Thoughts?
Lucas De Marchi