Just in case: Debian Bookworm comes with a buggy GCC
Tags: programming, cpp, linux, todayisearched
Published: 2023-12-15 11:27:09

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()`](https://en.cppreference.com/w/cpp/experimental/basic_string_view) being [missing in `std::basic_string_view`](https://en.cppreference.com/w/cpp/string/basic_string_view)), the most cryptic error was something like that:

```raw
/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](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329)).

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](https://www.debian.org/releases/bookworm/) of Debian, released in June this year. Previous stables version, Buster and Bulleye, came with [GCC 8](https://packages.debian.org/buster/gcc) and [GCC 10](https://packages.debian.org/bullseye/gcc ) 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.

1. You must use GCC 12, obviously. GCC 11 wasn't affected and the bug is fixed in GCC 13.
2. Optimizations must be enabled. You must use either `-O2` or `-O3`. `-O0`, `-O1` and `-Os` don't seem to concerned. If you set `CMAKE_BUILD_TYPE` to `Release`, CMake will add `-O3`. Your release builds will then be susceptible, unlike debug builds.
3. You must use C++20 and C++23. C++17 is unaffected.
4. 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](https://github.com/google/googletest/issues/4108)).
- 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
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:

```c++
#include <string>

std::string s;

int main() {
    s = "a"; // oopsi...
}
```

Here is another case that emits the warning:

```c++
#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:

```c++
s = "ab";
s = "ab" + std::string("c");
s = std::string("a") + "b";
```

## Circumvention Measures

The discussion for [bug 105329](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=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](https://packages.debian.org/testing/devel/gcc), so perhaps one day it will be available in the [backports](https://backports.debian.org/).

Fortunately, there are easy workarounds.

### Disable `-Wrestrict` globally

As a temporary solution, you may add `-Wno-restrict` to your build:

```cmake
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](https://dev.to/younup/cmake-on-stm32-episode-3-handle-warnings-in-3rd-party-files-4gg4):

```cmake
set_source_files_properties(
        main.cpp
        PROPERTIES
        COMPILE_FLAGS -Wno-restrict)
```

Alternatively, you can use a pragma at the beginning of each file:

```c++
#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:

```c++
#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:

```bash
$ 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:

```c++
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wrestrict"
#endif
```

See [Clang's builtin-macros](https://clang.llvm.org/docs/LanguageExtensions.html#builtin-macros).

## 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:

```c++
 s = "a";
 s = "a" + std::string("b");
 ```

Simply change them to:

```c++
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) 😆