Enforce minimum compiler version and use now-standard [[nodiscard]] in favor of compiler attribute
The replaces the custom compiler attribute with the directive standardized in C23. It looks like most major toolchains implemented this in 2020 (easy done I think since it was already a C++ feature that C adopted).
We should likely use this directive more liberally in libtock headers, but that's a different issue.
Hah, and then our CI goes and shows me since it's pinned to GCC version 10.3 (while GCC is on 15.something...) — will updated CI in a separate PR in a second
What version of GCC is needed for C23?
I'm worried we are getting out over our skis maybe, I think we should support all active Ubuntu LTSs, which I think includes jammy https://en.wikipedia.org/wiki/Ubuntu_version_history which I think is on GCC 10.3 https://launchpad.net/ubuntu/+source/gcc-arm-none-eabi
https://en.cppreference.com/w/c/compiler_support/23
But we are trying to use [[nodiscard]] which is listed as a "GCC 10" feature, but that is what our CI was pinned to https://github.com/tock/libtock-c/pull/540/files and that didn't work. So either I'm misunderstanding something or that table is somehow misleading.
OH COME ON. Okay, so, the chart is a lie, or sort of a lie.
If we read the GCC release notes for version 10, we learn
- The [[]] attribute syntax is supported, as in C++. Existing attributes can be used with this syntax in forms such as [[gnu::const]]. The standard attributes [[deprecated]], [[fallthrough]] and [[maybe_unused]] are supported.
If we read the GCC release notes for version 11, we learn
- The [[nodiscard]] standard attribute is now supported.
In practice, it seems that support for [[nodiscard]] was merged between 10.2 and 10.3, and the LTS package for the ARM compiler happens to be version 10.3 and for RISC-V the LTS packages version 10.2.
There is a meaningful behavior difference between [[nodiscard]] and the attribute approach, though we haven't hit this problem yet — perhaps it is an annoyance to downstream folks, harder to tell. More likely it becomes a problem if we follow through on annotating more of the functions that really should have this lint attached.
Somewhat amusingly, GCC 10.2 was released exactly five years ago July 23, 2020, with GCC 10.3 just shy of a year later on April 8, 2021. It's somewhat annoying that the LTS package for RISC-V is so far out of date :/.
The Jammy LTS is good through 2027, which would suggest that we are obligating ourselves to support a compiler that is 7 years out of date by then [for a project which primarily uses a language less than 10 years old!].
I might propose a compromise of a "minimum compiler version" driven by the most recent LTS, i.e., Noble Numbat, which has GCC 13 for RISC-V?
Somewhat amusingly, we did have an old, discarded check for GCC version (making sure you were at least on 5.1!).
I've restored that, and bumped CI to use the latest LTS.
I might propose a compromise of a "minimum compiler version" driven by the most recent LTS, i.e., Noble Numbat, which has GCC 13 for RISC-V?
I would favor the "minimum compiler version" driven by the oldest LTS. Realistically, there will be people who are using that.
However, I don't feel so strongly as to fight for it if others disagree.
I would favor the "minimum compiler version" driven by the oldest LTS. Realistically, there will be people who are using that.
I would like to make clear then that you are advocating for requiring support for compilers 7+ years out of date? Because that's the same thing in practice.
I would like to make clear then that you are advocating for requiring support for compilers 7+ years out of date? Because that's the same thing in practice.
Yup, I'm aware that's the outcome.
I guess some questions about that:
- Are there security vulnerabilities due to supporting an old compiler version? If there are, that would be a reason to upgrade, for sure.
- What's the process for getting access to a newer GCC version on LTS platforms? If that's trivial enough I'd be persuaded.
The downside with NOT supporting the compiler available on an LTS is that you'll get users who try to build Tock and fail out-of-the-box. A version check and good error message is a good portion of that battle, so it's good that we've got that.
Process
For arm? Trivial — you just download the zip from ARM's servers and install it (i.e., exactly what the arm embedded github action does).
For RISC-V? Equally trivial if you're willing to install third-party prebuild (e.g., https://xpack-dev-tools.github.io/riscv-none-elf-gcc-xpack/) — the threat model isn't really so different, it's just a little easier to swallow installing something for ARM built by ARM, rather than a build from (very mature) project written by a random internet person.
security
Do there exist bugs in older compilers which could theoretically lead to exploitable code? Yes, almost certainly, but actually exploiting those in practice feels nigh impossible.
I think the stronger case is that newer compilers offer newer features which can be used to make a codebase more correct / secure. E.g., to take as a random example the first bullet of the GCC 13 release notes (which is the version that "newest LTS" policy would let us use), we would gain access to the nullptr keyword, which lets compilers reason more precisely around various uses of "NULL" and NULL-like things.
The upside of breaking things for users seems limited. We get a new way to write code we have never written before and the ability to use one compiler directive instead of another? It's just no fun as a user to want to use something and then figure out you have to upgrade your entire OS just to compile some C code, or break your package manager. It would be even worse when you figure out that the break wasn't even because of needed capability to be compatible with Tock or some hardware, it was just syntax changes to checking return values and avoiding gotos. I would find that very annoying and off-putting, and I have a hard time accepting doing that to others.
And I say this knowing full well I was originally opposed to the objective of switching to stable Rust as leveraging new features seemed worth it. But now, I think there is value in stability.
And waiting 1.75 years, with a 7 year old compiler, just feels like the norm in this segment of computers to me. For example, it took 3.5 years to go from riscv builds (https://github.com/tock/libtock-c/pull/88) to riscv builds by default (https://github.com/tock/libtock-c/pull/353) just waiting on package managers and OSes to get their act together. It just kind of is what it is when you don't have rustup.
If we had better versioning of libtock somehow then maybe it would be reasonable to point users on an older OS to a specific release. However, I don't think we have ever gotten libtock to a reasonable state, although I think YWF is the last missing piece to get there. That doesn't solve the C library packaging headaches, however.
I will say that xpack does looks a bit like "rustup for GCC". The commands to install different versions are pretty trivial.
I think ultimately there's a lot of 'nice-to-haves' from newer compilers, but nothing that we need from newer compilers. If that changes, and there's really something that drives value, I might come back and advocate for something like "use xpack like rustup, but for the moment I'm convinced to leave this blocked until 2027.