Bug 98467 - gcc optimizes tapping code away
Summary: gcc optimizes tapping code away
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: tree-optimization (show other bugs)
Version: 11.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-12-28 22:58 UTC by Bernd Edlinger
Modified: 2020-12-29 16:03 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Bernd Edlinger 2020-12-28 22:58:13 UTC
Consider this test case:

$ cat test.cc
struct MyClass;
struct ptr {
    MyClass* get() { return t; }
    MyClass* t;
};
struct MyClass { void call(); };
void MyClass::call() {
    *(char*)(nullptr) = 1;
}
static void intermediate(ptr p) {
    p.get()->call();
}
int main() {
    intermediate(ptr{new MyClass});
}

$ g++ -g -O0 test.cc
$ ./a.out
Segmentation fault (core dumped)
$ g++ -g -Og test.cc
$ ./a.out
$ g++ -g -O1 test.cc
$ ./a.out
$ g++ -g -O2 test.cc
$ ./a.out
Segmentation fault (core dumped)
$ g++ -g -O3 test.cc
$ ./a.out
Segmentation fault (core dumped)
$ g++ -g -Ofast test.cc
$ ./a.out
Segmentation fault (core dumped)

It is somehow unexpected that this code is optimized
away only at -Og -O1, but not at very high or very low optimization levels.

I would even say although the code is of course invalid, it should
not be optimized away, as it might be a debug-code that intentionally taps.
Comment 1 Andrew Pinski 2020-12-28 23:08:51 UTC
This code is not invalid but undefined at runtime.  This means anything can happen.  In this case, GCC optimizes it away.
It is better to use __builtin_trap or __builtin_abort/std::abort if you want to get a trap going on.
Comment 2 Bernd Edlinger 2020-12-29 14:50:18 UTC
I debugged a bit in when we decide this function is const.
That appears to be in gcc/ipa-fnsummary.c:

/* Return true if T is a pointer pointing to memory location that is local
   for the function (that means, dead after return) or read-only.  */

bool
points_to_local_or_readonly_memory_p (tree t)
{
  /* See if memory location is clearly invalid.  */
  if (integer_zerop (t))
    return flag_delete_null_pointer_checks;
  if (TREE_CODE (t) == SSA_NAME)
    return !ptr_deref_may_alias_global_p (t);
  if (TREE_CODE (t) == ADDR_EXPR)
    return refs_local_or_readonly_memory_p (TREE_OPERAND (t, 0));
  return false;
}



And indeed the "problem" can be fixed by using -fno-delete-null-pointer-checks.
From the documentation in gcc/doc/invoke.texi I would never have guessed
what this option does here:

"@item -fdelete-null-pointer-checks
@opindex fdelete-null-pointer-checks
Assume that programs cannot safely dereference null pointers, and that
no code or data element resides at address zero.
This option enables simple constant
folding optimizations at all optimization levels.  In addition, other
optimization passes in GCC use this flag to control global dataflow
analyses that eliminate useless checks for null pointers; these assume
that a memory access to address zero always results in a trap, so
that if a pointer is checked after it has already been dereferenced,
it cannot be null."


It is only supposed to remove checks for null-pointer, not the other
way round...
Comment 3 Jakub Jelinek 2020-12-29 16:03:02 UTC
-fdelete-null-pointer-checks is generally an option that tells whether it is possible to have objects at address 0 (-fno-delete-null-pointer-checks) or not.
So the use above seems to be correct.
Anything can happen on UB and NULL pointer dereference is UB, so I don't see why you expect anything in particular.