https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52339
Jakub Jelinek <jakub at gcc dot gnu.org> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |jakub at gcc dot gnu.org, | |jason at gcc dot gnu.org, | |jsm28 at gcc dot gnu.org --- Comment #5 from Jakub Jelinek <jakub at gcc dot gnu.org> --- struct L; struct T { L *const l; T (L *x) : l(x) {} ~T () = default; T () = delete; T (const T &) = delete; T (T &&) = delete; void operator= (const T &) = delete; void operator= (T &&) = delete; }; struct L { T *t; L () = default; ~L () { delete t; } L (const L&) = delete; L (L&&) = delete; void operator= (const L &) = delete; void operator= (L &&) = delete; }; int main () { L *l = new L (); T *t = new T (l); l->t = t; delete t->l; } build_delete uses 5174 if (deleting) 5175 /* We will use ADDR multiple times so we must save it. */ 5176 addr = save_expr (addr); and for ((struct L *) this)->t in ~L destructor that wraps it in a SAVE_EXPR, for (struct L *) VIEW_CONVERT_EXPR<struct T *>(t)->l in main it doesn't, as the nop is stripped and the COMPONENT_REF is TREE_READONLY and !TREE_SIDE_EFFECTS. tree.cc (save_expr) documents that: Constants, and certain read-only nodes, are returned with no SAVE_EXPR because that is safe. Expressions containing placeholders are not touched; see tree.def for an explanation of what these are used for. So, the big question is whether it is just this use of save_expr in build_delete that needs stronger treatment (e.g. use addr = get_target_expr (addr, complain); instead of addr = save_expr (addr)) or if we should reconsider the save_expr implementation and wrap with SAVE_EXPR if the expression contains any INDIRECT_REFs/MEM_REFs (the latter perhaps with exception of ADDR_EXPR decl as address) despite it currently just returning the passed in expression, for fear that what the pointer points to could be deleted/reallocated in between. Is the following testcase valid C99+? If yes, I think we want to change SAVE_EXPR rather than the C++ FE. clang compiles it into something that passes, gcc evaluates p->a multiple times (to find out the size of the VLA and then again in the sizeof evaluation) and so it is a heap use after free (diagnosed e.g. by -fsanitize=address). #include <stdlib.h> struct S { int a; }; void bar (int *p, struct S *q) { free (q); } size_t foo (const struct S *p, struct S *q) { int b[p->a]; bar (b, q); return sizeof (b); } int main () { struct S *p = malloc (sizeof (struct S)); if (!p) return 0; p->a = 42; if (foo (p, p) != 42 * sizeof (int)) abort (); return 0; }