Skip to content

SIGFPE from -O2 compilation combining array division, float->double assignment ... and iostream? #71492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
roystgnr opened this issue Nov 7, 2023 · 1 comment

Comments

@roystgnr
Copy link

roystgnr commented Nov 7, 2023

It took me a while to boil down a much larger code to the point where I'm becoming confident there's no Undefined Behavior here:

#include <fenv.h>
#include <iostream>

template <typename T, typename D>
struct DualNumber
{ 
  DualNumber () : val(0), deriv(0) {}
  
  template <typename T2, typename D2>
  DualNumber (DualNumber<T2,D2>& a) :
    val(a.val), deriv(a.deriv) {}
  
  T val;
  D deriv;
};


template <std::size_t N, typename T>
struct NumberArray
{ 
  NumberArray (T a)
    { for (std::size_t i=0; i != N; ++i) _data[i] = a; }
  
  // Access a[i] via a._data[i] and no more SIGFPE!
  template <typename T2>
  NumberArray (NumberArray<N,T2>& a)
    { for (std::size_t i=0; i != N; ++i) _data[i] = a[i]; }
  
  T& operator[](std::size_t i)
    {
      // Remove this assert, even just std::endl, and no more SIGFPE!
      if (!(i < N)) { std::cerr << "Assert failed." << std::endl; }
      return _data[i]; }
  
  NumberArray<N,T>& operator/= (const T& a)
    { for (std::size_t i=0; i != N; ++i) _data[i] /= a; return *this; }
  
  T _data[N];
};

static const unsigned int N = 9; // 9 or 10 SIGFPEs
// static const unsigned int N = 8; // 8 or fewer doesn't SIGFPE

int main(int, char *[])
{
  feenableexcept(FE_INVALID);
  
  typedef float Scalar;  // double doesn't SIGFPE!
  DualNumber<Scalar, NumberArray<N, Scalar>> dn;
  
  // No copy made, no SIGFPE!
  DualNumber<Scalar, NumberArray<N, Scalar>> dncopy{dn};
  
  // Changing only one member doesn't SIGFPE!
  // 2 (or 2.0) in both denominators doesn't SIGFPE!
  // Modifying dn *or* dncopy gives a SIGFPE!
  dncopy.val /= 3.0;
  dncopy.deriv /= 3.0;
  
  DualNumber<double, NumberArray<N, double>> broken_conversion{dn};
  
  return 0;
} 

Trying out all the clang++ versions I have handy on Ubuntu 23.10:

clang++ 16.0.6 or 15.0.7 -O2 give a SIGFPE when running this
clang++ -O1, -O0, or -O3 give no SIGFPE
clang++ 14.0.6 or 13.0.1 give no SIGFPE
clang++ -fsanitize=undefined,address -O2 gives no warnings ... and no SIGFPE!

I'm noticing that a lot of slightly-similar issues are getting marked as duplicates of #6422 or #8472, but nothing here is touching the rounding mode and nothing here should be triggering a FP exception, so this may be an independent bug. I do need that endl-emitting assertion in the code path, or the bug doesn't trigger, which confuses me further. I'm not sure what FP shenanigans iostream (from libstdc++, not libc++, in my Ubuntu clang packages) might be pulling ... or that might be an obvious red herring, since that assertion never fails in this code, and so iostream functions should never be getting called.

@nikic
Copy link
Contributor

nikic commented Nov 7, 2023

There is UB here, at the line feenableexcept(FE_INVALID). Whether your source code contains operations that produce a division by zero is irrelevant, as they may be legally introduced by the compiler.

Please see https://clang.llvm.org/docs/UsersManual.html#controlling-floating-point-behavior for related options to control FP exception behavior.

@nikic nikic closed this as not planned Won't fix, can't repro, duplicate, stale Nov 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants