Home Dashboard Directory Help
Search

VC12 pollutes the floating point stack when casting Infinity/NaN to unsigned long by d.major


Status: 

Closed
 as Duplicate Help for as Duplicate


5
0
Sign in
to vote
Type: Bug
ID: 806362
Opened: 10/23/2013 2:19:31 PM
Access Restriction: Public
1
Workaround(s)
view
3
User(s) can reproduce this bug

Description

When C++ code casts a double to unsigned long, VC12 generates a call to library function _dtoui3. If the double is infinite or NaN, the _dtoui3 function performs an unbalanced push onto the processor's floating point stack. If this happens eight times, the floating point stack becomes completely full, and subsequent floating point stack operations may fail. This is a regression from previous versions of Visual Studio.

The sample program performs some operations in a loop eight times. It is expected that all of these operations return the same result every time they are called. However, by the eighth iteration, the floating point stack is depleted, and DoSomeMath has unexpected behavior. This is a contrived sample, but it is based on code from a real application.

Expected result:

CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42

Actual result:

CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: 42
CastToULong: 0 DoSomeMath: -2147483648

------------------------------------------------------------------------------

#include <stdio.h>
#include <math.h>

__declspec(noinline) void CastToULong(double d)
{
    unsigned long ul = static_cast<unsigned long>(d);
    printf("CastToULong: %u\t", ul);
}

__declspec(noinline) void DoSomeMath(int x)
{
    double d = ceil(x * 0.0034);
    int n = static_cast<int>(d);
    printf("DoSomeMath: %d\n", n);
}

int main(int argc, char* argv[])
{
    for (int i = 0; i < 8; i++) {
        CastToULong(INFINITY);
        DoSomeMath(12345);
    }
    return 0;
}
Details
Sign in to post a comment.
Posted by Bruce Dawson on 5/16/2014 at 3:06 PM
A better link to the duplicate bug is https://connect.microsoft.com/VisualStudio/feedback/details/808199 -- I was able to create this from the number supplied below. Thanks for that.

I have confirmed that this bug is fixed in VS 2013 Update 2.
Posted by Charles Fu on 1/13/2014 at 11:13 AM
This is a duplicate of https://connectadmin/Feedback/ConnectTab.aspx?FeedbackID=808199

We are aiming to ship the fix in next VS Update (Spring). In the meantime, I attached a private file ftol3.obj with the fix (The file might take several minutes or hours to appear).

Sorry for the bug.

Charles Fu
Visual Studio C++ Team.
Posted by Bruce Dawson on 1/11/2014 at 9:19 PM
This bug also exists in VS 2013. It looks like it will block us from switching from VS 2010.

I notice that the bug has been closed as a duplicate. Unfortunately there is no indication of what bug it is a duplicate and there is no apparent route to getting further feedback on this bug.
Posted by d.major on 10/28/2013 at 7:53 PM
It has been pointed out to me that casting infinity to uint is undefined in C++11. Perhaps the compiler's behavior here is technically not incorrect, but still, leaving an unbalanced stack is pretty destructive! Please consider this bug from the compatibility perspective at least.
Posted by Microsoft on 10/23/2013 at 9:29 PM
Thanks for your feedback.

We are rerouting this issue to the appropriate group within the Visual Studio Product Team for triage and resolution. These specialized experts will follow-up with your issue.
Posted by Microsoft on 10/23/2013 at 2:51 PM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)
Sign in to post a workaround.
Posted by qsylvr on 1/12/2014 at 8:36 PM
I've authored a runtime workaround for this bug which should be future-proof against any MS fixes, including a potential runtime DLL patch that only some users might have installed. Works with all four CRT options (DLL/Static, Debug/Release). Drop this file into every module (DLL or EXE) which you build with VS2013.



#if defined(_M_IX86) && _MSC_VER >= 1800 && _MSC_VER < 1900

#pragma warning(disable:4075) // C4075: warning about unknown initializer section
#pragma init_seg( ".CRT$XCB" ) // section sorts alphabetically before any user code section

extern "C" void _except1();
extern "C" void _ftol3_except();
extern "C" int __stdcall VirtualProtect( void*, int, int, int* );

// ABI specifies empty FPU stack on entry and exit of a non-inlined function
static __declspec(noinline) unsigned __int32 ConvertInfinityToUint32()
{
    volatile double zero = 0.0;
    return (unsigned __int32)(1.0 / zero);
}

static __declspec(naked) void HackedEndOfFtol3Except()
{
    _asm call _except1
    _asm fstp st( 0 )
    _asm add esp, 0x20
    _asm ret
}

static bool PatchVS2013FPUStackBug()
{
    // If the top-of-stack bits of the status word changed, then converting
    // an infinity or NaN to uint32 has leaked an FPU stack entry.
    volatile short swBefore, swAfter;
    _asm fnstsw swBefore
    ConvertInfinityToUint32();
    _asm fnstsw swAfter
    if ( (swBefore & 0x3800) == (swAfter & 0x3800) )
        return true; // already patched, either by MS or by this hack

    // Fix the FPU stack, then fix the _ftol3_except function itself
    _asm fstp st( 0 )

    char* p = (char*)&_ftol3_except;
    // Edit-and-continue builds use a relative jump to protect the actual function.
    if ( p[0] == '\xE9' )
    {
        p = p + 5 + *(int*)(p + 1);
    }
    // The _ftol3_except function begins with SUB ESP, 20 then WAIT; call to _except1 is at 57 bytes
    // (relative offset for call is bytes 58-62) followed by ADD ESP,0x20 then the final RET.
    if ( p[0] == '\x83' && p[1] == '\xEC' && p[2] == '\x20' && p[3] == '\x9B' &&
        p[57] == '\xE8' && p[62] == '\x83' && p[63] == '\xC4' && p[64] == '\x20' && p[65] == '\xC3' )
    {
        // Replace call to _except1 with a jump to HackedEndOfFtol3Except
        int old = 0x20;
        VirtualProtect( p + 57, 5, 0x40, &old );
        p[57] = '\xE9';
        *(int*)(p + 58) = (int)&HackedEndOfFtol3Except - (int)(p + 62);
        VirtualProtect( p + 57, 5, old, &old );
        return true;
    }
    return false;
}

#pragma comment(linker, "/include:_g_bPatchedVS2013FPUStackBug")
extern "C" bool g_bPatchedVS2013FPUStackBug = PatchVS2013FPUStackBug();

#endif
File Name Submitted By Submitted On File Size  
ftol3.obj.bin 1/13/2014 8 KB