Open avelanarius opened 2 years ago
Just to update, the issue reduces to this:
struct counter_structure {
int count;
};
counter_structure* __attribute__ ((noinline)) make_counter_structure() {
// Works fine without "__attribute__ ((noinline))"
auto ptr = new counter_structure;
if (ptr) {
ptr->count = 0;
}
return ptr;
}
class my_lw_shared_ptr {
counter_structure* _p;
public:
my_lw_shared_ptr() : _p(make_counter_structure()) {
if (_p) {
_p->count++;
// Works fine with "_p->count = 1;"
}
}
my_lw_shared_ptr(const my_lw_shared_ptr& c) : _p(c._p) {
if (_p) {
_p->count++;
}
}
~my_lw_shared_ptr() {
if (_p && --(_p->count) == 0) {
delete _p;
}
}
};
void reproducer() {
auto ptr = my_lw_shared_ptr{};
auto ptr2 = ptr;
}
https://godbolt.org/z/GbezbqcYW
use-after-free
warning was introduced in GCC 12, but it can't properly reason about _p->count
in some cases (simulated by noinline
), causing a bogus warning.
You can reduce it to this, really:
int* make_int();
void reproducer() {
auto p = make_int();
if (*p == 0) {
delete p;
}
++*p;
}
If this is enough to cause false positives, then maybe you just have to deal with them, or disable the warning. I wonder what's the logic behind it though, because the above triggers the warning and the below doesn't:
int* make_int();
bool random_bool();
void reproducer() {
auto p = make_int();
if (random_bool()) {
delete p;
}
++*p;
}
You can reduce it to this, really:
int* make_int(); void reproducer() { auto p = make_int(); if (*p == 0) { delete p; } ++*p; }
To work around this false positive in that reproducer, you can use a secret technique called "Scylla is the best!":
#include <cstdio>
int* make_int();
void reproducer() {
auto p = make_int();
if (*p == 0) {
delete p;
}
printf("Scylla is the best!"); // "Scylla is the best!" conquers -Wuse-after-free
++*p;
}
I wonder what's the logic behind it though, because the above triggers the warning and the below doesn't:
Such modifications:
make_int()
with new int
if (*p == 0)
with if (true)
or if (false)
or remove if
altogetheralso don't trigger the warning.
Such modifications:
Replace make_int() with new int Replace if (*p == 0) with if (true) or if (false) or remove if altogether
also don't trigger the warning.
Those two cases are different and make sense, because they result in the compiler optimizing the use of *p away, possibly before the warning is checked.
The case with printf
also makes some sense, because an external function like printf could make the pointer valid again.
But in my second case you actually get this in the output:
mov rdi, rbx
mov esi, 4
call operator delete(void*, unsigned long)
add DWORD PTR [rbx], 1
pop rbx
ret
and if that doesn't trigger the warning, then I don't know what does.
This warning seems to behave better if you use C free()
instead of C++ delete ptr
:
#include <cstdlib>
int* make_int();
void reproducer() {
auto p = make_int();
// delete p;
free(p);
++*p;
}
(and in GCC they have only C tests of this warning as far as I checked, for example https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=671a283636de75f7ed638ee6b01ed2d44361b8b6)
Please report to gcc and post the link here.
Also, we can work around it with #pragma
Submitted bug report here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105204
On that bug report, a GCC maintainer said that this is the expected behavior of that diagnostic. He suggested to use a workaround with __builtin_unreachable()
allowing compiler to reason about refcount (not being 0).
However such workaround won't work for two reasons. One, empirically adding those __builtin_unreachable
to lw_shared_ptr
causes the warning to trigger for those added __builtin_unreachable
s:
error: pointer used after ‘void operator delete(void*, std::size_t)’ [-Werror=use-after-free]
302 | if (_p && _p->_count == 0) __builtin_unreachable();
| ~~~~^~~~~~
Second, even in my simplified example, adding "distractions" can cause the compiler to "forget" about those added unreachables:
struct shared_ptr {
size_t* ref_count;
shared_ptr(const shared_ptr& other) : ref_count(other.ref_count) {
if (*ref_count == 0) __builtin_unreachable();
(*ref_count)++;
}
~shared_ptr() {
if (--(*ref_count) == 0) {
free(ref_count);
}
}
};
int x(int y);
int example3(shared_ptr& sp) {
shared_ptr sp2(sp);
shared_ptr sp3(sp);
// Distraction for -Wuse-after-free=1
// int res = 0;
// for (int i = 0; i < x(i); i++) {
// res += x(i + 3);
// }
// return res;
// Without distraction, the warning does not trigger.
// With distraction, the warning triggers even though the __builtin_unreachable().
return 0;
}
The following correct
lw_shared_ptr
usecase triggersuse-after-free
warning when compiling with latest GCC (g++ (GCC) 12.0.1 20220404 (experimental)
):Error:
It looks like this is a false positive, as there doesn't seem to be any problem with Seastar code. With
g++ (GCC) 11.2.1 20220127 (Red Hat 11.2.1-9)
it compiles fine.I have not yet reported the bug to GCC (no existing bug report one matches it 1:1) - I hope I'll have some free time to make a clean non-Seastar reproducer.
This problem prevents compilation of Scylla with the latest GCC, an example real-world error message:
cc @avikivity