-Werror Is not your friend (2017)
embeddedartistry.com
embeddedartistry.com
A previous company I worked at had large C++ projects that built with 5000+ warnings. Nobody looked at them, obviously. Buried within them I recall one about printf-style formatting string mismatching arguments, that said something like “mismatching arguments will cause a crash if this code is executed.” I only noticed it buried in the list of warnings after debugging a segfault caused by that line of code.
Werror tends to be placed eagerly pass the buck along to anyone building it, not just CI or developers. I used to feel that Werror was a good thing, but nowadays anything with CI on merge requests will tell me about any warnings, and someone wanting to build the software shouldn't get penalized for having a different build environment: there's a reason they aren't thrown as errors to begin with. Does this make sense?
I’ve worked on dozens of non-legacy codebases and in every single one we don’t have warnings last for long, because going from zero to a few warnings is annoying. I don’t want that spam in my logs and I definitely don’t want suspicious constructs in my codebase. For the ones that enable -Werror, by the way, I always disable it so I can work and clean things up before committing. It’s just basic hygiene and discipline, the same as formatting your code before sending a PR. If your team doesn’t have that then it has bigger problems.
In open source projects most of the ones I work on have a handful of regular contributors and a couple dozen one-off or other irregular contributions. In many cases we don't get all the warnings on every system that anyone uses but if someone goes "fix warnings for GCC 13 on Alpha" or whatever environment they're using we are more than happy to take their changes, and if it warns in CI or on any of our machines we'll either bring it up during PR review or submit a change soon afterwards to fix it. I'm too young to have projects more than a few years old but there's a decent number of other open source projects with a long history and many contributors that compile with no warnings on say their Linux CI and maybe a handful if you pass it through Apple Clang (since they don't test that much).
(Off-topic) Oh how I wish this were possible in the language I use at $dayjob. Swift does not let you silence warnings on a line by line basis. It’s easily one of the top 5 worst things about the language.
I can understand the argument for open-source projects that aren't attached to a company, but otherwise? Maybe this is my bias being in a subindustry centered on distributed binaries and so have set standard toolchains (i.e. what the build machines use), but I'm still not seeing it. This seems like the more harmful practice than using -Werror
Obviously, what gets built and deployed to production is not built off my computer; it goes through the standard CI they have.
I once worked at a place that could flood the error/warning logs in two seconds (Ruby). Unless it was causing an issue, all warnings etc were ignored.
Is that the kind of codebase you want to work in?
Most of the c++ I've written used the arduino build system, so an absurd amount of things that'd normally be errors or warnings are just permitted and expected.
Similarly, let’s say we’re talking about an open source project that users may download and compile from source. A portable open source project shouldn’t needlessly break if you use a newer or different compiler.
So:
- Do make the source code as clean as you can, using all tools available, including -Werror. Putting -Werror in your continuous build should be fine too.
- When a user downloads your library and compiles it in order to use it, the make target (or equivalent) that they use shouldn’t compile with -Werror, because we don’t know which compiler they have or which warnings that enables.
I think it is the only sane way for C++.
Same in gcc, it will give you wrong warnings like -Wstrict-aliasing.
Changing toolchains is a very rare occurrence because the subtle differences will almost always cause problems. When someone suggests changing toolchains, they owe everyone a VERY CONVINCING argument. After which the changed warnings will be the LEAST of your problems as you migrate.
Toolchain upgrades will cause new warnings to surface as the detection gets better. But you DO want to fix them, don't you? You ARE a professional, aren't you? You DO care about your product's quality, don't you?
After over a decade without werror, followed by a decade with werror, I'd take werror every time. Warnings exist for a reason, and in the case of C compilers, VERY GOOD reasons that you really should address.
At my workplace, we use werror, static analysis, various lints, detekt... everything that can issue warnings about your code quality. If any of them complain, you can't merge your PR, because we're professionals.
For example I ran into this issue with the AWS C++ SDK ( https://github.com/aws/aws-sdk-cpp/ ). And then I had to go into several CMake files and remove Werror. What do I as a user care about warnings? Can I only use that SDK once I have fixed (probably more like silenced) all the warnings?
At least they added a option now after a few years of this annoyance: https://github.com/aws/aws-sdk-cpp/issues/2555
It doesn’t work for a monorepo where the toolchain needs to be upgraded for many projects at once. Enabling each new warning (turning it into a build-breaking error) may require touching hundreds of files. Getting a toolchain upgrade done in one monster commit doesn’t work; you will inevitably break something and have to roll back, or be unable to commit it all due to merge conflicts with other commits.
I’ve been on a team that needed to be able to upgrade the toolchain in one commit and then turn on each new warning in separate commits. Having an explicit list of all the warnings that are turned on and then gradually updating it to be the equivalent of -Wall was the way to go.
werror is your friend.
From different platforms to users having very different versions of a compiler.
linters and staic anaylsis did not really exist then
And even if ignore MSVC specific arguments, you’d still have to deal with multiple versions and variants of clang + gcc
But -Werror for a dev target that e.g. CI uses to compile-test pull requests on known toolchains and distros that you explicitly intend to support is useful. The alternative would be tons of effort to skip -Werror but still have CI flag that pull request that introduces new warnings. Just having the build fail because -Werror is far simpler and less fragile.
IMHO it's similar to the robustness principle/Postel's law: "be conservative in what you send, be liberal in what you accept". Your build should pass without warnings on your system, but your build system should allow warnings on your customer's system.
Distros already enforce a consistent toolchain. Arch, Debian, Fedora, Gentoo, etc.
Distros like Arch and Alpine keep the patching to a minimum, most packages need no patches. RedHat/Fedora and Ubuntu do patch significantly more. Debian is in the middle ... there was an infamous incident where they patched openssl to fix an uninitialized-memory warning, and caused a huge security issue for many people: https://www.schneier.com/blog/archives/2008/05/random_number... (yes, openssl sucks, but still.) So this is why distros take their cleanup patches upstream. Distro patches are for real observed bugs that aren't fixed in an upstream release (or not in the desired major release branch), or to just disable -Werror in projects that don't know better.
The biggest and rather frustrating issue left is that warnings are heavily tool depended, not just in that new tools will produce new warnings, but in that different tools will produce incompatible warning. The workaround that makes the warning go away in one tool, will create a warning in the other. You can end up with a lengthy back&forth before you find something that makes all the tools happy and you frequently run into situation were you are really just pleasing the tools, not fixing any actual issues.
there’s so many ways to skin that cat.
Accepting a patch like that can be a significant commitment of time and brain power. "What exactly does this fix? Does it actually fix it?". Such patches can be a lot more complicated than a straightforward new feature.
And then there's that oddball compilers may not support everything gcc and clang do, so making them happy may actually mean sacrificing something.
In open source you never know what compiler someone will use. You can probably say gcc 3.0 isn't supported, but someone might have just built the latest master clang with a new warning added.
Warnings spilling over from library dependencies can be mitigated easily by including them using -isystem instead of -I.
For code that is not libraries, there is nothing strange about having dependency on a specific toolchain, preferably two supported options.
Besides, warnings or not, devs using different toolchains than CI is just inviting for works-on-my-machine situations the rest of us left behind in the last century.
> This flag adds a toolchain dependency to your project. Newer compiler versions (or alternative compiler vendors) are likely to generate new warnings, making the build fail.
IMO most large projects will anyway have multiple toolchain dependencies. Ever tried to compile Chrome using a different compiler/build system? And why would different contributors use different toolchains anyway? If you want to contribute, just use the same tools everyone else on the project uses.
When upgrading compilers, incompatible warnings are going to be the least of your worries. So what is the problem that the author wants to solve by not using -Werror? He agrees that warnings should not be merged. So why not stop them right when they're written?
>>> We primarily use Clang, and it is a common occurrence that our CI server will report a warning when compiling our “warning-free” code with GCC.
... my only point here is: FIX YOUR TOOL CHAIN why do you want to use Clang if all your dependency relies on GCC?
Then use -Werror=<warning-name> for each such diagnostic.
The problem there is that GCC fails if you give it an option it doesn't recognize, including just a diagnostic one.
If you use -Werror=funky-new-warning, it will fail on an older GCC that doesn't have funky-new-warning.
To handle differences in GCC/Clang flags you just have to detect the compiler (trivial with CMake), or dictate the compiler.
But only do that for your developers and CI. Don't use `-Werror` for downstream users of your project.
Including downstream. I don't want people writing me patches that trigger my pet warnings.
This is entirely compatible with the idea of locally only turning on -Werror, and then disabling certain warnings that you don't intend to fix, without foisting -Werror onto downstream.
You can still have non-negotiable warnings that you turn on rather than off, and those do go downstream too.
They will fail your CI. There's no issue.
It's better if the deal-breaker diagnostics are on for everyone, so that everyone is working in the same programming language dialect.
But since my libraries don't ship with build system files those build process decisions are my own and don't "leak" to the users of my libraries (e.g. it wouldn't even enter my mind to dictate a specific build system or build settings on my library users, I know how bad it is to deal with overengineered cmake files of other people).
When integrating other libraries I usually also just throw away the 1000+ lines of cmake files they come with and replace them with a few dozen lines of cmake script of my own (I wish that would be an exaggeration or a joke, but it's not - especially when it comes to "professional" C++ libraries coming out of Khronos or Google).
When updating the toolchain you can set default flags to disable the new warnings if you wish to fix each warning one a time.
That said, my preference is for “dev” builds to not have warnings as errors, only “retail” which gets built by CI.
Third party libraries almost always have to be compiled without warnings as errors because almost all open source code is rife with warnings.
Cross-platform makes this fun. I work on a project that, in production, runs on an embedded Linux box. With a couple of features disabled, I can do development work just fine on my Macbook. Unfortunately, the embedded target uses a relatively old and crusty version of GCC and OS X has a newer version of Clang. We generally shoot for "zero warnings on production builds" but warnings in development on OS X are... okay. We pay attention to them and have occasionally benefitted from warnings that Clang provides that the older GCC does not, but some of the warnings come from some (admittedly questionable but work correctly) casts that a 3rd party library's header makes. Definitely not worth the effort to try to get upstream to fix the code gen that makes these headers to make OS X Clang happy.
We vendor all third-party dependencies but would probably bias towards fixing the warning. It just spirals out of control too fast if you don't. Ancient toolchains for embedded or similar is definitely tough.
If you can use modern toolchains then the combination of warnings across GCC + Clang + MSVC is probably a win.
In edge cases, we get maybe 2 versions of GCC to choose from in a distribution, but nobody's special enough to get their own just because it's officially supported. If it compiled and tested directly, your project gets built with v1.2.3 like everything else.
Why? Distros should run the actual build without trying to swap out the toolchain with something else. If a new compiler version causes a crash of performance regression that will look bad on the original developer.
For the rest of us? -Werror is not our friend.
Why? I don't expect people to build against their system libraries.
>unless your build system actually automatically downloads and uses the right clang version automatically
Nix, bazel, buck, etc all support defining the toolchain for your build. Reproducable builds are useful. You are supporting a known configuration. Crashes can all be collected an easily analyzed. You can be sure you have a properly compiled version by verifying the hash.
>Most of us aren't using such a build system.
That's okay. You can just ingest a prebuilt artifact if you aren't capable of tying it into your build system.
Because that's what people do. One of the big reasons that build open source software is to have fun and work together with other hobbyists, some of each don't even write software professionally at all. If you want to have fun with open source you really want to keep things simple and keep the entry barrier low. Otherwise, it'll just feel like an unpaid job.
Building from source would use the toolchain you designed the source code for meaning that it will always succeed. If someone wants to fork the project and add support for a different toolchain they are free to do so. If they want to get rid of the requirement to have no compiler errors they are free to remove -Werror or disable the new warnings.
You don't need to install a toolchain to use it. The build tool should handle everything for you and should handle clearing caches of unsued toolchains that are no longer needed.
>but -Werror adding a very unnecessary roadblock is just not nice.
Warnings should be looked at. If the errors are not important you can add flags to silence them.
I don't want a dozen clangs and gccs wasting disk space (& increasing initial build times) regardless of who manages them! And I don't mean one project having a bunch of toolchains (I pray that never happens), I mean having dozens of random projects on my computer, most having been built just once and which won't be touched for an indeterminate amount of time.
> Warnings should be looked at. If the errors are not important you can add flags to silence them.
Yes, developers should look at them (and developers can look at them even without -Werror! or, as said elsewhere, it could be enabled conditionally). Users won't get much out of them, other than being annoyed at the project if they prevent the project from being built.
You don't have to persist all of the build time dependencies forever. It can redownload them if you need to rebuild one of them.
Most of your suggestions here make sense under the assumption that the project in question is one whose development I participate in/with some regularity engage with, but that simply isn't the case for most projects a given person may have some desire/need to build from source.
You can either manually run the cleanup after the build or when you need free space. Alternatively a systemd timer that deletes old stuff that hasn't been accessed in weeks would work too.
The same issue exists even only the system toolchain is used. There will me gigabytes of dependencies or object files that aren't needed taking up space.
This is a problem of all dependencies. Toolchain dependencies are not special.
Among the couple dozen things I've got built from source, most have their object files sum up to less than the 117MB of a libLLVM-15.so.1, and if not, are only slightly larger (200-300MB). (and, as for the topic in question, maybe only one downloaded its own toolchain, and everything else freely allowed me to give a local gcc or clang in 'CC='/'CXX=').
I think a build of all targets of QEMU is the only significantly larger thing I have (1.9GB of binaries, 2.1GB of object files).
> Alternatively a systemd timer that deletes old stuff that hasn't been accessed in weeks would work too.
Besides being awful (why does a build system need to even know about systemd?!?!!? much less make a semi-permanent addition), that also would fail if I moved the build directory to somewhere else (which, granted, would probably mean a clean would be needed to rebuild, but I will not care when I'm moving all of my git clones from one drive to another).
I'm happy you're not responsible for most of the FOSS projects I compile.
I prefer modern languages like Go, because all these warnings and errors are just part of the default "go build":
package hello
func world() int {
println("world")
} // hello.go:5:1: missing returnThat’s pretty close to rustc.
And I get that you can’t throw that at a giant codebase in one day, and I get that dev builds probably drop —Werror. Thrrr are realities.
But new stuff not under clang-tidy, cppcheck, and pvs if you’re feeling speedy? People do that?
There's a good chance your old code will compile if you don't use -Werror, or even if you do, but whether it runs the way it used to is a different question entirely.
But isn't that exactly what the C standard has said Undefined Behavior is, for as long as it has existed?
Also here on Planet Earth, performance optimization has become much less important than not breaking older code that previously worked, but nobody told the GCC devs.
Never change, HN. Keep working toward a Utopia that never was and never will be. And keep downvoting helpful hints indicating additional things to watch out for in the context of the article.
Why aren't they using the old version of the compiler? Keep your unoptimized, ill-formed code if you want, while everyone else can benefit from the newest features. You can't have your cake and eat it too. Either you want to use the new stuff and you'll have to adapt your code, or you don't want the new stuff and you can keep using your code as is.
Sadly, that's the dilemma many embedded developers eventually have to face. I'd personally like to use modern C++ features when working on a legacy codebase that I inherited. Even if I don't want or need that sort of thing, newer compilers are also better at flagging potential errors and UB (hence the article.) Overall, CPUs are much faster now, and memory is much cheaper. So I can afford to trade off a bit of overhead for better code quality and engineering discipline.
And yes, newer compilers are also better at optimization, but that's literally the last thing I care about in the embedded business. I'm not targeting 8051s and 8-bit AVRs anymore.
Unfortunately, what I can't afford is the time needed to review 200K+ LOCs to find all the hidden gotchas that will be exposed by newer, more aggressive compilers. There may be bugs that should have been fixed years ago, but that never surfaced. Some will trigger new warnings (again, hence the article) but will all of them be flagged?
My whole point is that dealing with a broken build due to -Werror is so far down in the noise it's not even worth mentioning. If I fix the issues responsible for the new error messages and walk away thinking my job is done, well... maybe this is my lucky day, and maybe it isn't.
I agree there's sometimes a certain lack of pragmatism, and that UB is used as the ultimate argument that trumps all arguments. But on the other hand, it really is UB, so that seems more of an argument for "we want a migration path" rather than "never change it until the end of time".