Just in case: Debian Bookworm comes with a buggy GCC
Pierre Gradot
Posted on December 15, 2023
Last month, I embarked on a new project. I set up a new computer with the latest Debian version, installed my favorites tools, and was all set to code. My first task was to migrate all the repositories from C++14 to C++20. While it might seem as straightforward as updating all the CMakeLists.txt
to replace set (CMAKE_CXX_STANDARD 14)
with set (CMAKE_CXX_STANDARD 20)
reality proved otherwise (and I knew it would).
Among the funny things I encountered (such as std::experimental::basic_string_view::to_string()
being missing in std::basic_string_view
), the most cryptic error was something like that:
/usr/include/c++/12/bits/char_traits.h:431:56: warning: ‘void* __builtin_memcpy(void*, const void*, long unsigned int)’ accessing 9223372036854775810 or more bytes at offsets -4611686018427387902 and [-4611686018427387903, 4611686018427387904] may overlap up to 9223372036854775813 bytes at offset -3 [-Wrestrict]
431 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
This is a known bug in GCC and it affects only the 12.x branch. It's (Bug 105329 - [12/13/14 Regression] Bogus restrict warning when assigning 1-char string literal to std::string since r12-3347-g8af8abfbbace49e6).
Encountering this bug might not be an everyday occurrence, but it's quite easy to trigger it. Unfortunately, as you have guessed, Debian Bookworm ships with this problematic version of GCC. Bookworm is the latest stable version of Debian, released in June this year. Previous stables version, Buster and Bulleye, came with GCC 8 and GCC 10 respectively. Since Debian releases a new stable version every two years, if you are using Debian like me, we may have to deal with this bug for an extended period.
In this article, I will explain how you can trigger this bug and how you can avoid it. It's also an opportunity to revisit various techniques for circumventing spurious warnings.
Conditions to Trigger the Bug
Beyond from the source code itself, specific build conditions are necessary to trigger the bug.
- You must use GCC 12, obviously. GCC 11 wasn't affected and the bug is fixed in GCC 13.
- Optimizations must be enabled. You must use either
-O2
or-O3
.-O0
,-O1
and-Os
don't seem to concerned. If you setCMAKE_BUILD_TYPE
toRelease
, CMake will add-O3
. Your release builds will then be susceptible, unlike debug builds. - You must use C++20 and C++23. C++17 is unaffected.
- You must pass the
-Wrestrict
flag since it causes the warning. It is included in-Wall
.
This means your build may break simply by:
- Updating GCC (it happened to GoogleTest).
- Changing from debug to release mode (but I guess your CI always builds in release mode, right?).
- Switching for C++17 or below to C++20 or above (and I encourage you to regularly upgrade to the new standard version).
- Enabling warnings (no, I can't believe you had lived without
-Wall
so far).
I use the word "break" because most release builds also use -Werror
to turn warnings into errors, ensuring they are absolutely noticeable.
The Code Itself
Oh, poor boyz and girlz... The code is so simple.
Here is a CMake CMakeLists.txt
:
cmake_minimum_required(VERSION 3.27)
project(buggy)
add_executable(buggy main.cpp)
target_compile_options(buggy PRIVATE -Wall)
target_compile_features(buggy PRIVATE cxx_std_20)
And here a main.cpp
that will raise the dreaded warning:
#include <string>
std::string s;
int main() {
s = "a"; // oopsi...
}
Here is another case that emits the warning:
#include <string>
std::string s;
int main() {
s = "a" + std::string("b"); // oopsi...
}
The string literal must be single byte and must be the left-hand-side operand. The following lines are fine:
s = "ab";
s = "ab" + std::string("c");
s = std::string("a") + "b";
Circumvention Measures
The discussion for bug 105329 clearly indicates, in comments 26 and 27, than the 12 branch won't be fixed. The solution is hence to move to GCC 13. Alas! Debian will probably not change the major version of GCC in Bookworm. The next release, Trixie, is currently in testing mode and comes with GCC 13, so perhaps one day it will be available in the backports.
Fortunately, there are easy workarounds.
Disable -Wrestrict
globally
As a temporary solution, you may add -Wno-restrict
to your build:
target_compile_options(buggy PRIVATE -Wall -Wno-restrict)
This is extreme and I highly discourage pushing such a change to a develop
or a master
/main
branch.
Disable -Wrestrict
for some files
Another solution I also consider temporary is to disable the warning but only for a particular set of files.
You can use CMake for this, especially if you can't change these sources files:
set_source_files_properties(
main.cpp
PROPERTIES
COMPILE_FLAGS -Wno-restrict)
Alternatively, you can use a pragma at the beginning of each file:
#pragma GCC diagnostic ignored "-Wrestrict"
#include <string>
std::string s;
int main() {
s = "a";
}
Disable -Wrestrict
locally
Disabling the warning only for a couple of lines is a more acceptable solution:
#include <string>
std::string s;
int main() {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wrestrict"
s = "a";
#pragma GCC diagnostic pop
}
I had to use this technique around a #include <boost/test/data/test_case.hpp>
because BOOST_DATA_TEST_CASE
triggered the bug. This solution is very verbose and reduce your code's readability. Think twice before choosing it.
Note about Clang
Clang does support #pragma GCC
but it doesn't support -Wrestrict
... It means that the previous codes will raise a warning or error:
$ clang++ main.cpp -Wall -Werror
main.cpp:7:32: error: unknown warning group '-Wrestrict', ignored [-Werror,-Wunknown-warning-option]
#pragma GCC diagnostic ignored "-Wrestrict"
If you want your code to compile both with GCC and Clang, you will need some extra preprocessor tricks:
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wrestrict"
#endif
The preferred solution: change the code
The solution with set_source_files_properties()
may be acceptable if you plan to switch to GCC 13 soon. Otherwise, if you plan to use GCC 12 for a long time, just change your code.
Remember the two faulty lines:
s = "a";
s = "a" + std::string("b");
Simply change them to:
s = std::string("a");
s = std::string("a") + std::string("b");
This simply makes implicit conversions explicit.
Conclusion
At the end of the day, the warning message is quite scary, but the code can easily be changed to avoid the bug. Apart from changing the code to avoid this particular issue with GCC 12, we also discussed several techniques you may use to mitigate spurious warnings in your code.
This is not the first time I run into a compiler's bug. It's always so much fun (sarcasm indented) 😆
Posted on December 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.