lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] feholdexcept()


From: Vadim Zeitlin
Subject: Re: [lmi] feholdexcept()
Date: Mon, 9 Jan 2017 00:53:39 +0100

 Hello,

 I'm very sorry, I completely misunderstood how feholdexcept() worked and,
worse, didn't actually check that it worked the way I thought it did. After
looking closer into it, feholdexcept() only seems to prevent floating point
exceptions from being turned into "real" asynchronous exceptions. This is
rather confusing because there is nothing in the standard allowing to
enable turning FP exceptions into real exceptions in the first place -- but
the idea is, apparently, that the caller might do it in some non-portable
way (e.g. GNU libc feenableexcept() or MSVC _controlfp() or just, indeed,
direct control register manipulation using inline assembler) and the
standard at least provides a way to for libraries to protect themselves
against this by using feholdexcept() on entry to the library function and
feupdateenv() when leaving it in order to avoid unexpected exceptions being
thrown.

 However -- and, again, I'm very sorry for not understanding nor testing it
before -- feholdexcept() doesn't actually affect setting the bits of the
control word at all. So, as can be seen by running this simple program:

---------------------------------- >8 --------------------------------------
#include <cfenv>
#include <cstdio>

using namespace std;

void show(char const* when)
{
    printf("%s:\t%08x\n", when, fetestexcept(FE_ALL_EXCEPT));
}

int main(int argc, char const* argv[])
{
    if (feclearexcept(FE_ALL_EXCEPT) != 0) {
        perror("feclearexcept");
        return 1;
    }

    fenv_t env;
    if (feholdexcept(&env) != 0) {
        perror("feholdexcept");
        return 2;
    }

    show("Initially");

    float x = argc / 193.0f;

    show("After div");

    return (int)x;
}
---------------------------------- >8 --------------------------------------

which outputs

---------------------------------- >8 --------------------------------------
Initially:      00000000
After div:      00000020
---------------------------------- >8 --------------------------------------

They will still be set irrespectively of whether feholdexcept() was called
or not.

 And this means that my changes were completely wrong because we can't
check that 0 == std::fetestexcept(FE_ALL_EXCEPT) in fenv_is_valid() as this
check fails all the time and, in fact, I've implemented a nice DoS attack
against myself in this way, because I can't even close lmi currently as it
just keeps spawning message box after message box... This is a problem in
its own right, I think we should have some limit on the number of message
boxes shown (per minute?) but in the meanwhile we need to just remove this
check for fetestexcept() and, probably, feholdexcept() call as well as it's
useless. Here is the trivial patch doing just this:

---------------------------------- >8 --------------------------------------
diff --git a/fenv_lmi.cpp b/fenv_lmi.cpp
index 351400d..db5e9af 100644
--- a/fenv_lmi.cpp
+++ b/fenv_lmi.cpp
@@ -65,8 +65,6 @@ void fenv_initialize()
 #if defined LMI_X87
     x87_control_word(default_x87_control_word());
 #else  // !defined LMI_X87
-    std::fenv_t saved_env;
-    LMI_ASSERT(0 == std::feholdexcept(&saved_env));
     LMI_ASSERT(0 == std::fesetround(FE_TONEAREST));
     // Standard C++ provides no way to set hardware precision.
     // Here is an example of a C99 7.6/9 extension that controls
@@ -164,7 +162,7 @@ bool fenv_is_valid()
 #if defined LMI_X87
     return default_x87_control_word() == x87_control_word();
 #else  // !defined LMI_X87
-    return FE_TONEAREST == std::fegetround() && 0 == 
std::fetestexcept(FE_ALL_EXCEPT);
+    return FE_TONEAREST == std::fegetround();
 #endif // !defined LMI_X87
 }

@@ -184,7 +182,6 @@ std::string fenv_explain_invalid_control_word()
     oss
         << "The floating-point environment unexpectedly changed."
         << "\nThe rounding mode is " << std::fegetround()
-        << " and the exception bitmask is " << std::fetestexcept(FE_ALL_EXCEPT)
         << ".\n"
         ;
 #endif // !defined LMI_X87
---------------------------------- >8 --------------------------------------

 Finally, if you're wondering why/where does the original FP exception
happen in the first place, it is in grow_lf70() function in wx/hashmap.h
header which is used by wxHashMap<> which is itself used quite extensively
by wxWidgets and happens to be called during each event loop iteration,
which explains the infinite flood of message boxes. I don't know why does
this function use division instead of multiplication, except that it has
always been like this, ever since wxHashMap was added 15 years ago. I can
(and probably will) modify it, but it doesn't change the fact that checking
fetestexcept() result is still wrong as it's clearly very easy to do
something resulting in a FP exception if a simple division like above can
do it (or a simple multiplication: multiplying 193 by 0.85 still raises
precision loss flag), and we don't want to show a message box (let alone
tons of them) when this happens.

 Sorry once again for this very stupid mistake,
VZ


reply via email to

[Prev in Thread] Current Thread [Next in Thread]