9

As I am receiving "Floating point division by zero" exception when using TWebBrowser and TEmbeddedWB from time to time, I discovered that I need to mask division by zero exceptions Set8087CW or SetMXCSR.

Q1: What would be the best approach to do this:

  1. to mask such exceptions early in the application startup and never touch them again (the app is multithreaded)?
  2. to use OnBeforeNavigate and OnDocumentComplete events to mask / unmask exceptions? (is there a chance that exception could occur after the document is loaded?)

Q2: What would be the best "command" to mask only "division by zero" and nothing else - if application is 32-bit is there a need to mask 64 bit exception too?

The application I am using it it has TWebBrowser control available all the time for displaying email contents.

Also, if anyone can clarify - is this a particular bug with TWebBrowser control from Microsoft or just difference between Delphi/C++ Builder and Microsoft tools? What would happen if I would host TWebBrowser inside Visual C++ application if division by zero error would appear - it wouldn't be translated into exception but what would happen then - how would Visual C++ handle "division by zero" exception then?

It is kind of strange that Microsoft didn't notice this problem for such a long time - also it is strange that Embarcardero never noticed it too. Because masking floating point exception effectively also masks your own program exception for that particular purpose.

UPDATE

My final solution after some examination is:

SetExceptionMask(GetExceptionMask() << exZeroDivide);

The default state from GetExceptionMask() returns: TFPUExceptionMask() << exDenormalized << exUnderflow << exPrecision. So obviously, some exceptions are already masked - this just adds exZeroDivide to the masked exceptions.

As a result every division by zero now results with +INF in floating point instead of exception. I can live with that - for the production version of the code it will me masked to avoid errors and for the debug version it will be unmasked to detect floating point division by zero.

Coder12345
  • 3,431
  • 3
  • 33
  • 73
  • What's wrong with try-except? – Johan Oct 04 '13 at 18:02
  • Can you provide an SSCE? I am using EmbeddedWB in various applications and I never encountered the need for masking exceptions... – whosrdaddy Oct 04 '13 at 18:09
  • I got the error in `TEmbeddedWB` and it occurs rarely. The problem is not with that component but with the underlying `TWebBrowser`. People can reproduce it under various circumstances (by clicking scrollbars, just by loading content and so on). – Coder12345 Oct 04 '13 at 18:16
  • 1
    @whosrdaddy There are about a gazillion questions on this topic on SO – David Heffernan Oct 04 '13 at 18:16
  • 2
    I had the same issue when [embedding YouTube clips in the TWebBrowser](http://stackoverflow.com/questions/8200581/twebbrowser-crashes-with-embedded-youtube-clips). I ended up masking the exception on application start-up. there was no (known) impact on my application ever since. – kobik Oct 05 '13 at 10:20

1 Answers1

10

Assuming that you have no need for floating point exceptions to be unmasked in your application code, far and away the simplest thing to do is to mask exceptions at some point in your initialization code.

The best way to do this is as so:

SetExceptionMask(exAllArithmeticExceptions);

This will set the 8087 control word on 32 bit targets, and the MXCSR on 64 bit targets. You will find SetExceptionMask in the Math unit.

If you wish to have floating point exceptions unmasked in your code then it gets tricky. One strategy would be to run your floating point code in a dedicated thread that unmasks the exceptions. This certainly can work, but not if you rely on the RTL functions Set8087CW and SetMXCSR. Note that everything in the RTL that controls FP units routes through these functions. For example SetExceptionMask does.

The problem is that Set8087CW and SetMXCSR are not threadsafe. It seems hard to believe that Embarcadero could be so inept as to produce fundamental routines that operate on thread context and yet fail to be threadsafe. But that is what they have done.

It's surprisingly hard to undo the mess that they have left, and to do so involves quite a bit of code patching. The lack of thread safety is down to the (mis)use of the global variables Default8087CW and DefaultMXCSR. If two threads call Set8087CW or SetMXCSR at the same time then these global variables can have the effect of leaking the value from one thread to the other.

You could replace Set8087CW and SetMXCSR with versions that did not change global state, but it's sadly not that simple. The global state is used in various other places. This may seem immodest, but if you want to learn more about this matter, read my document attached to this QC report: http://qc.embarcadero.com/wc/qcmain.aspx?d=107411

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Can't you pause all threads, set the exceptionmask and then resume them again. what's that call again. `Synchronize`? – Johan Oct 04 '13 at 19:19
  • @Johan No, you cannot do that. Read my linked doc. – David Heffernan Oct 04 '13 at 19:41
  • Is it then the same effect if I use instead `SetExceptionMask(TFPUExceptionMask() << exZeroDivide);` to at least keep other FPU exceptions (exInvalidOp, exDenormalized, exOverflow, exUnderflow, exPrecision)? I only need to mask division by zero really. (there is no exAllArithmeticExceptions in 2010 version I use). – Coder12345 Oct 04 '13 at 21:38
  • You can do what you want. ;-) I'm not sure exactly which exceptions the web browser control throws. Are you doing any floating point arithmetic in your app? – David Heffernan Oct 04 '13 at 21:42
  • Not really, and if I do, I can live with +INF result instead of exception. Actually, now I see that by default some exceptions are already masked - default state is `TFPUExceptionMask() << exDenormalized << exUnderflow << exPrecision`, so this would add `exZeroDivide` to the list. I'll do it like this - `SetExceptionMask(GetExceptionMask() << exZeroDivide);`. Thanks for your answer. – Coder12345 Oct 04 '13 at 22:01
  • @DavidHeffernan The pdf on the qc page is corrupted. I rename the file to the root name in the file header to "Rethinking Delphi Floating Point Control Register Management.pdf". Both of my PDF reader applications fail to read it. The current ActiveX control createFactory() method within C++Builder 10.1 Berlin needs to invoke Set8087CW(0x133F) before the new operator. If not, importing the ActiveX control to Visual Studio 2015, Update 3's Toolbox. Use the control on a VS Windows Form causes VS to crash and restart. Cause: Uncaught System.Arithmetic exception from mscorlib.dll. – CPlusPlus OOA and D Mar 19 '17 at 13:16
  • It's perhaps a zip file. Look at the header bytes in a hex editor. – David Heffernan Mar 19 '17 at 13:17
  • @DavidHeffernan You are exactly correct. Once I open it with 7zip, I obtain the unzipped pdf and it successfully opens. Thank you very much. – CPlusPlus OOA and D Mar 19 '17 at 13:20
  • If anyone is looking for 32-bit versus 64-bit alternatives, then review these on the Berlin release: [System.Math.SetExceptionMask](http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Math.SetExceptionMask) and [Floating-Point Number Control Routines](http://docwiki.embarcadero.com/RADStudio/Berlin/en/Floating-Point_Number_Control_Routines) – CPlusPlus OOA and D Mar 19 '17 at 13:50
  • Note that [QualityCentral has now been shut down](https://community.embarcadero.com/blogs/entry/quality-keeps-moving-forward), so you can't access `qc.embarcadero.com` links anymore. If you need access to old QC data, look at [QCScraper](http://www.uweraabe.de/Blog/2017/06/09/how-to-save-qualitycentral/). – Remy Lebeau Jun 09 '17 at 18:09
  • Here is a thread-safe version I wrote that can set and get the 8087 control word values. This has been only tested in Delphi 5 as that is the only version I have access to. https://gist.github.com/fyndor/4335833f9b7e5418114cdf33cb9b6e43 – Greg Cobb Jun 23 '17 at 20:36
  • @fyndor The bigger problem is the RTL which won't use your threadsafe methods. Dealing with that us delicate. My QC report explains how, but Emba recently killed QC, thus killing my report. – David Heffernan Jun 23 '17 at 20:43
  • I'm using Delphi 5 and a search of the original function found the declaration and implementation and one instance in an OpenGL unit that itself is never referenced. I assume it must be making a hidden call to that original set function at least once, but even if it is I am the one making the threads and so I have control over them. Maybe this is all different in later versions of Delphi but in D5 it seems to work just fine. I am using this so I can embed my Delphi code in a C# application and to keep things straight I am putting the Delphi code in a separate thread. Seems to work for me. – Greg Cobb Jun 26 '17 at 16:14
  • @Fyndor, D5 is just as screwed as all other versions in this regard. Probably your code doesn't happen to be affected by the problem. It is a little obscure. – David Heffernan Jun 26 '17 at 16:28