I had a little bit of downtime and decided it might be interesting to look at adding a static analyzer into vscode alongside particle workbench.
Had some moderate success using clang-tidy (now out of the box in microsoft c++). I passed clang-tidy the arm-none-eabi toolchain inside the particle sdk, but even with attempting to use the additional compiler flags that the make system uses, I was unable to get the DeviceOS headers to pass static asserts and a number of other forced checks.
Perhaps clang-tidy isn't a great tool for this because it doesn't have great support for gcc-arm.
Does anyone have suggestions regarding other tools to use to improve code quality or if I'm missing something in my clang-tidy setup and this should totally be doable?
I haven't tried to use clang-tidy but it would not surprise me that you could not pass your full firmware through it successfully.
What I would do is factor your code into unit-testable pieces that mostly don't use Device OS. This subset might be workable with a code analyzer. Even without using a static code analyzer, I use this technique for off-device unit testing. Instead of building with gcc-arm, I build with the native compiler for unit tests which is way faster and easier. One of the most useful techniques I've found with this is running natively under valgrind under Linux. This makes it easy to detect memory leaks, buffer overrun, underrun, using freed blocks, etc.. It also makes it easier to run coverage tests (gcovr) using the native compiler.
In order to test more code I use UnitTestLib which contains a small subset of the Device OS features like String and some other things that are useful and not cloud-related. These probably won't pass static analysis either, but might be fixable, unlike trying to get all of Device OS to pass.
You're right clang-tidy works nicely with clang, and that's it, you'l have a hard time making it play with gcc and make. That being said, I think there are four good approaches.
cpplint -- this is a little python script that will just scan the directory you ask it too, so sick it on main/src and call it a day -- also super easy to integrate into an action/runner
cppcheck -- this takes a little more tlc to setup, nothing major though and you'll want to turn off a few things like ignoring headers -- especially ignoring headers
extend the build.mk -- you can rewrite the includes to change them to system includes and then add extra warnings; you can get a lot of mileage out of gcc's warning system, especially -Werror= to change them to outright errors.
use static_assert and constexpr -- seriously, this is lightyears easier than trying to get any sort of "real" unit testing in, for things that you can't do at compile time use asserts -- it's firmware -- you should be anyway
this is bonus -- don't get too hung up on static analyzers -- in embedded, white-box testing is more important -- guard your entry and exit values, add sanity checks, don't pack your structs, and scan padding variables for memory corruption -- this will find a LOT more bugs that trying to prognosticate what your unit tests need to do
I think @nonarkitten's list is a good one. I've become a big fan of git's pre-commit hooks, if that applies to your workflow. Here are some links that can help you automate cppcheck, cpplint, clang-tidy, clang-format, and a host of other checks to run on each invocation of git commit ....
None of these tools is a silver bullet as noted above, but I have caught a number of bugs (and improved code quality) by having checks like these run on every commit. They do requires some initial tuning/setup, but it's a one-time cost. I also don't shy away from ignoring elements or entire external libraries that I pull in for my builds. I too make judicious use of GCC flags, and use static_asserts around critical pieces that are required for inter-operation with other systems (e.g. binary structs that get passed between different devices/firmware).