Tcl Source Code

View Ticket
Login
Ticket UUID: 1061224
Title: "expr sqrt(-1)" crash with EXCEPTION_FLT_INVALID_OPERATION
Type: Bug Version: obsolete: 8.5a2
Submitter: davygrvy Created on: 2004-11-05 22:32:45
Subsystem: 50. Embedding Support Assigned To: kennykb
Priority: 5 Medium Severity:
Status: Closed Last Modified: 2004-11-12 00:04:24
Resolution: Invalid Closed By: davygrvy
    Closed on: 2004-11-11 17:04:24
Description:
Stack trace:
KERNEL32! RaiseException
MSVCRTD! _raise_exc
MSVCRTD! _87except
MSVCRTD! _startOneArgErrorHandling
MSVCRTD! sqrt
TclExecuteByteCode
TclCompEvalObj
Tcl_EvalObjEx
NamespaceEvalCmd
Tcl_NamespaceObjCmd
TclEvalObjvInternal
TclExecuteByteCode
TclCompEvalObj
Tcl_EvalObjEx
Tcl_CatchObjCmd
TclEvalObjvInterna
TclExecuteByteCode
Tcl_ExprObj
Tcl_ExprBooleanObj
Tcl_IfObjCmd
TclEvalObjvInternal
Tcl_EvalEx
Tcl_Eva
fake_Tcl_Eval

The script I asked it to eval was "expr sqrt(-1)". 
When my fake_Tcl_Eval() modifies the registers as you
suggested, the app killing exception 0xC0000090 is avoided:

int
fake_Tcl_Eval (Tcl_Interp *interp, char *string)
{
    Tcl_DString dst;
    int result;

    Tcl_DStringInit(&dst);

    // ensure no upper ascii character combos look like
    // UTF-8 start codes.
    //
    Tcl_ExternalToUtfDString(XiRCEncoding, string, -1,
&dst);

    int mask = _controlfp( 0, 0 );
    // Some math functions need to be protected from
raising
    // exceptions: /tcl expr sqrt(-1)
    _controlfp( _CW_DEFAULT, _MCW_EM );

    result = Tcl_Eval(interp, Tcl_DStringValue(&dst));

    _controlfp( mask, _MCW_EM );
    Tcl_DStringFree(&dst);
    return result;
}

Can Tcl assume the responsibility of protecting it's
own math calls by this method rather than the client of
the API?
User Comments: davygrvy added on 2004-11-12 00:04:24:
Logged In: YES 
user_id=7549

Ok, I give in.  This shouldn't be Tcl's responsibility.  I
research more on this, and it's just a nasty behavior in a
mixed Borland (exe) and MS compiled (dll) application just
like you jenglish had explained it.

davygrvy added on 2004-11-09 10:20:14:
Logged In: YES 
user_id=7549

Initial call in my bridge:

struct Tcl_NotifierProcs us = {
    Tcl_SetTimer,
    XiRCwaitForEventProc,
    0L,
    0L,
    Tcl_InitNotifier,
    Tcl_FinalizeNotifier,
    Tcl_AlertNotifier,
    Tcl_ServiceModeHook
};
originalW4EP = (Tcl_WaitForEventProc *)
GetProcAddress(hTclMod, "Tcl_WaitForEvent");
Tcl_SetNotifier(&us);  // Need to specify our own!


Thunk stuff ended up being:
struct ThunkData {
    int cwords;
};

void
EnterToMsvcrt (ThunkData *data)
{
    // See
https://sourceforge.net/support/tracker.php?aid=1061224
    data->cwords = _controlfp(0, 0);
    _controlfp(CW_DEFAULT, 0xFFFF);
}

void
LeaveFromMsvcrt (ThunkData *data)
{
    _clearfp();
    _controlfp(data->cwords, 0xFFFF);
}

#define ENTER \
    ThunkData data; EnterToMsvcrt(&data)

#define LEAVE \
    LeaveFromMsvcrt(&data)

All the exports to the app are protected by ENTER and LEAVE:
int
fake_Tcl_EvalFile (Tcl_Interp *interp, char *fileName)
{
    ENTER;
    int result = Tcl_EvalFile(interp, fileName);
    LEAVE;
    return result;
}

My Tcl_WaitForEvent got a bit tricky, though, to prevent an
infinite loop:
int
XiRCwaitForEventProc (Tcl_Time *timePtr)
{
    ENTER;
    int result;
    struct Tcl_NotifierProcs stuff = {
Tcl_SetTimer,
originalW4EP,
0L,
0L,
Tcl_InitNotifier,
Tcl_FinalizeNotifier,
Tcl_AlertNotifier,
Tcl_ServiceModeHook
    };

    Tcl_SetNotifier(&stuff);
    result = originalW4EP(timePtr);
    stuff.waitForEventProc = XiRCwaitForEventProc;
    Tcl_SetNotifier(&stuff);
    LEAVE;
    return result;
}

I'll make a note here if this passes the acid test I'll
start working on now.  I think I've got XiRCwaitForEventProc
upside down, but not sure yet.

davygrvy added on 2004-11-09 08:15:15:
Logged In: YES 
user_id=7549

Having to create my own notifier to manage the non-parented
calls is not "less difficult" as jenglish would have
thought.  This will take me a couple days or so to get working.

davygrvy added on 2004-11-07 02:36:21:
Logged In: YES 
user_id=7549

The problem here is that normally, hardware exceptions are
caught by the C runtime (msvcrt) unless changed.  As Tcl is
not specifically resetting the runtime's exception mask for
each time a math function is called, the control mask
follows the global settings.  As part of my application is
not accible to me in source, I can not change the rest of
the DLLs usage of the global control mask.  But if Tcl
dis-associated itself to a global exception setting and was
responsible just for its own calls, all is fixable.

davygrvy added on 2004-11-07 01:29:56:
Logged In: YES 
user_id=7549

no

nobody added on 2004-11-07 00:55:20:
Logged In: NO 


On Linux/Alpha, similar difficulties
were long ago solved with a
-mieee switch to gcc.  Isn't there
something similar on your platform?

davygrvy added on 2004-11-06 07:30:49:

File Added - 107772: patch.txt

davygrvy added on 2004-11-06 07:30:48:
Logged In: YES 
user_id=7549

Here's another, slightly cleaner patch.

davygrvy added on 2004-11-06 06:55:49:

File Added - 107767: patch.txt

Logged In: YES 
user_id=7549

modified ExprUnaryFunc() in tclExecute.c to support
saving/restoring and clearing math errors.

Is this bad?  ExprBinaryFunc and the others would be next I
guess.  What if they all called a common Save() and
Restore/Clear() so the #ifdefs just sit one function?

davygrvy added on 2004-11-06 06:24:54:
Logged In: YES 
user_id=7549

Personally, I don't think it should be my responsibility to
handle the math errors that the core throws to me as a user
of the API.

davygrvy added on 2004-11-06 06:08:12:
Logged In: YES 
user_id=7549

Adding _clear87(); before resetting the mask solves this. 
Good, but should this be the responsibility of Tcl itself,
not for me as a client of the API?

davygrvy added on 2004-11-06 05:47:28:
Logged In: YES 
user_id=7549

Wait!   That trick works with a msvcrtd.dll loaded, but not
the release build.  I can't seem to trace it.  The exception
appears to be raised _after_ the Tcl_Eval that invoked it.

Attachments: