You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello,
The way Python4delphi is changing exception masks is not secure. Care is needed mainly to remove exceptions from the mask.
It's something that affects the entire application and other libraries like Skia, OpenGL, ActiveX, etc...
The problem is specifically in the MaskFPUExceptions function in PythonEngine.pas, which is used in the initialization of P4D-Data- Sciences
There are a few ways to resolve this. One of them would be to create a class to store the mask's state, change it, so that after a try finally you can restore the previous mask.
@viniciusfbb Thanks for the rapid reply and explanation. I sorta thought this was most likely MaskFPUExceptions (hence the issue being tentatively in this repo)
I wrote a replacement for MaskFPUExceptions that stores the current GetExceptionMask when called with True in a global then replaces it when called with false. This worked a treat and Skia now works perfectly again (Skia was really acting up without this fix...).
@lmbelo While my hack works I'm not submitting a PR for this one as this is a complicated issue which is situational - my 'fix' works for my app but other packages may well mess with the Exception Mask so a more robust solution is really required.
For reference here's what I did - you can enable/disable it via the define (good for testing)...
{$DEFINE USESAFEMASK}
var
FPUMASK: TArithmeticExceptionMask;
procedure SafeMaskFPUExceptions(ExceptionsMasked : boolean;
MatchPythonPrecision : Boolean = True);
implementation
uses
Maths;
{$R *.fmx}
procedure SafeMaskFPUExceptions(ExceptionsMasked : boolean;
MatchPythonPrecision : Boolean);
begin
{$IFDEF USESAFEMASK}
{$IF Defined(CPUX86) or Defined(CPUX64)}
if ExceptionsMasked then
begin
FPUMASK := GetExceptionMask;
SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision]);
end
else
SetExceptionMask(FPUMASK);
{$WARN SYMBOL_PLATFORM OFF}
{$IF Defined(FPC) or Defined(MSWINDOWS)}
if MatchPythonPrecision then
SetPrecisionMode(pmDouble)
else
SetPrecisionMode(pmExtended);
{$WARN SYMBOL_PLATFORM ON}
{$IFEND}
{$IFEND}
{$ELSE}
MaskFPUExceptions(ExceptionsMasked, MatchPythonPrecision);
{$IFEND}
end;
The exception mask is undone after the importation of any of the Python packages. If you have a look at the exception mask routine you'll see that the exception mask is being undone (for CPUX86 and CPUX64) to Delphi's default.
If we're changing exception mask in somewhere out of modules importation, we should be restoring to the defaults when we're done.
Can you show specifically where it is breaking down? Which platform are you targeting to?
Hope I can be of any help.
with each of those using shared beforeinstall, afterinstall, beforeimport, afterimport which calls my SafeMaskFPUExceptions() function - this didn't work :(
You need to get extra attention when targeting installed Python versions on Linux. You might hit across known issues reported before here. Have a look in all closed issues when you're going afters Linux errors.
Only for the sake of getting you ahead, you can't use apt installed Python working with the DelphiFMX library.
We need a Python executable linked statically or dynamically with the interpreter shared library, or an executable that exposes interpreter's symbols some how. The is not the case of Python available by apt.
@lmbelo, you didn't understand my post. The P4D-Data-Sciences code is wrong and python4delphi's MaskFPUExceptions implementation should be changed as well. I will explain it better.
Note that the code above arbitrarily changes the exception mask to:
[exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision] when the ExceptionsMasked parameter is True [exDenormalized, exUnderflow, exPrecision] when the ExceptionsMasked parameter is False
Now, look at one of the P4D-Data-Sciences import code:
procedureTTorchVision.ImportModule;
begin
MaskFPUExceptions(true);
tryinherited;
finally
MaskFPUExceptions(false);
end;
end;
As it stands, if the exception mask before the code above is empty [], after import it will be [exDenormalized, exUnderflow, exPrecision]. Likewise, if the exception mask is exAllArithmeticExceptions (all exceptions), which is what happens when using OpenGL, D2D, GDIP, Skia, among others, after import the mask will be [exDenormalized, exUnderflow, exPrecision]. And there is no way to predict the mask that will be configured in the application when it arrives at the P4D-Data-Sciences import code, there are many variants, there are several other libraries that change like the Delphi XML lib itself, and it can even vary according to the application (VCL or FMX).
So, the correct thing is: save the mask value, change it and after importing the P4D-Data-Sciences, restore the previous mask value and not arbitrarily change it to [exDenormalized, exUnderflow, exPrecision].
I made a possible fix for both codes. First for python4delphi's MaskFPUExceptions:
procedureMaskFPUExceptions(ExceptionsMasked: Boolean;
var StoredMask: TArithmeticExceptionMask; MatchPythonPrecision: Boolean);
begin{$IF Defined(CPUX86) or Defined(CPUX64)}if ExceptionsMasked then
StoredMask := SetExceptionMask(GetExceptionMask + [exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision])
else
SetExceptionMask(StoredMask + [exDenormalized, exUnderflow, exPrecision]); // Maybe a SetExceptionMask(StoredMask); would be better, but I'm not sure if removing the " + [exDenormalized, exUnderflow, exPrecision]" would cause problems in python4delphi{$WARN SYMBOL_PLATFORM OFF}{$IF Defined(FPC) or Defined(MSWINDOWS)}if MatchPythonPrecision then
SetPrecisionMode(pmDouble)
else
SetPrecisionMode(pmExtended);
{$WARN SYMBOL_PLATFORM ON}{$IFEND}{$IFEND}end;
And now one of the P4D-Data-Sciences import codes:
procedureTTorchVision.ImportModule;
var
StoredMask: TArithmeticExceptionMask;
begin
MaskFPUExceptions(true, StoredMask);
tryinherited;
finally
MaskFPUExceptions(false, StoredMask);
end;
end;
@lmbelo, you didn't understand my post. The P4D-Data-Sciences code is wrong and python4delphi's MaskFPUExceptions implementation should be changed as well. I will explain it better.
Note that the code above arbitrarily changes the exception mask to:
[exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision] when the ExceptionsMasked parameter is True [exDenormalized, exUnderflow, exPrecision] when the ExceptionsMasked parameter is False
Now, look at one of the P4D-Data-Sciences import code:
procedureTTorchVision.ImportModule;
begin
MaskFPUExceptions(true);
tryinherited;
finally
MaskFPUExceptions(false);
end;
end;
As it stands, if the exception mask before the code above is empty [], after import it will be [exDenormalized, exUnderflow, exPrecision]. Likewise, if the exception mask is exAllArithmeticExceptions (all exceptions), which is what happens when using OpenGL, D2D, GDIP, Skia, among others, after import the mask will be [exDenormalized, exUnderflow, exPrecision]. And there is no way to predict the mask that will be configured in the application when it arrives at the P4D-Data-Sciences import code, there are many variants, there are several other libraries that change like the Delphi XML lib itself, and it can even vary according to the application (VCL or FMX).
So, the correct thing is: save the mask value, change it and after importing the P4D-Data-Sciences, restore the previous mask value and not arbitrarily change it to [exDenormalized, exUnderflow, exPrecision].
I made a possible fix for both codes. First for python4delphi's MaskFPUExceptions:
procedureMaskFPUExceptions(ExceptionsMasked: Boolean;
var StoredMask: TArithmeticExceptionMask; MatchPythonPrecision: Boolean);
begin{$IF Defined(CPUX86) or Defined(CPUX64)}if ExceptionsMasked then
StoredMask := SetExceptionMask(GetExceptionMask + [exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision])
else
SetExceptionMask(StoredMask + [exDenormalized, exUnderflow, exPrecision]); // Maybe a SetExceptionMask(StoredMask); would be better, but I'm not sure if removing the " + [exDenormalized, exUnderflow, exPrecision]" would cause problems in python4delphi{$WARN SYMBOL_PLATFORM OFF}{$IF Defined(FPC) or Defined(MSWINDOWS)}if MatchPythonPrecision then
SetPrecisionMode(pmDouble)
else
SetPrecisionMode(pmExtended);
{$WARN SYMBOL_PLATFORM ON}{$IFEND}{$IFEND}end;
And now one of the P4D-Data-Sciences import codes:
procedureTTorchVision.ImportModule;
var
StoredMask: TArithmeticExceptionMask;
begin
MaskFPUExceptions(true, StoredMask);
tryinherited;
finally
MaskFPUExceptions(false, StoredMask);
end;
end;
@viniciusfbb does Skia changes the exception mask for the whole app life cycle?
Well, regarding Windows x86 (made by FLDCW and FSTCW ???) and x64, the FPSCR flag equivalent is saved across thread context switches. I'm almost sure ARM has a similar approach, I think they save it in the _FPSCR array (I need to confirm this).
Those masks we are setting up on P4D are the ones required by Python.
Well, regarding Windows x86 and x64, the FPSCR flag is saved across thread context switches. I'm almost sure ARM has a similar approach, I think they seve it in the _FPSCR array (I need to confirm this). Those masks we are setting up on P4D are the ones required by Python.
Not quite. Change the main, run a thread and you will see the value of the mask of the main in the thread... Until a few years ago SetExceptionMask was not even thread safe, but I believe this has already been fixed.
Anyway, just changing the import of P4D-Data-Sciences the way you suggest is not the way I think is the most correct but it will hardly cause problems.
Well, just tried it for the sake of testing. Definitely changing it on a second thread doesn't affect the main thread. It proves that it is saved across thread context switches.
Still not sure that doing that change is the way to go.
I didn't say that changes to background threads would affect the main thread's exception mask. In my view, threads being created in the background would have the initial values of exception mask of the main thread.
However, I have confirmed it through testing, and it is not true. New threads copy the values of mask exceptions from the thread that created it, not from the main thread. So your change is safe too.
SafeMaskFPUExceptions(True);
FTask := TTask.Run(
procedure()
begin
TThread.Synchronize(nil,
procedure()
begin
MainModule.delphi_style();
end
)
end
);
SafeMaskFPUExceptions(False);
It uses a Skia shader while the thread is running showing data reported back from that thread (it's a boring progress display)
Activity
viniciusfbb commentedon Aug 19, 2022
Hello,
The way Python4delphi is changing exception masks is not secure. Care is needed mainly to remove exceptions from the mask.
It's something that affects the entire application and other libraries like Skia, OpenGL, ActiveX, etc...
The problem is specifically in the MaskFPUExceptions function in PythonEngine.pas, which is used in the initialization of P4D-Data- Sciences
There are a few ways to resolve this. One of them would be to create a class to store the mask's state, change it, so that after a try finally you can restore the previous mask.
peardox commentedon Aug 19, 2022
@viniciusfbb Thanks for the rapid reply and explanation. I sorta thought this was most likely MaskFPUExceptions (hence the issue being tentatively in this repo)
I wrote a replacement for MaskFPUExceptions that stores the current GetExceptionMask when called with True in a global then replaces it when called with false. This worked a treat and Skia now works perfectly again (Skia was really acting up without this fix...).
@lmbelo While my hack works I'm not submitting a PR for this one as this is a complicated issue which is situational - my 'fix' works for my app but other packages may well mess with the Exception Mask so a more robust solution is really required.
For reference here's what I did - you can enable/disable it via the define (good for testing)...
lmbelo commentedon Aug 21, 2022
The exception mask is undone after the importation of any of the Python packages. If you have a look at the exception mask routine you'll see that the exception mask is being undone (for CPUX86 and CPUX64) to Delphi's default.
If we're changing exception mask in somewhere out of modules importation, we should be restoring to the defaults when we're done.
Can you show specifically where it is breaking down? Which platform are you targeting to?
Hope I can be of any help.
peardox commentedon Aug 21, 2022
Ahh, that explains why using afterimport to reset the mask didn't work...
I tried two methods...
import(packagea)
import(packageb)
import(packagec)
with each of those using shared beforeinstall, afterinstall, beforeimport, afterimport which calls my SafeMaskFPUExceptions() function - this didn't work :(
So, I've not got
SafeMaskFPUExceptions(True)
import(packagea)
SafeMaskFPUExceptions(False)
See https://github.com/peardox/Lartis/blob/main/PythonSystem.pas#L501
lmbelo commentedon Aug 21, 2022
You should decorate it by a try finally block.
peardox commentedon Aug 21, 2022
I'm going to - this was a recent test, still unfinished - no proper error handlers yet
I just discovered that I need to handle errors properly anyway (Linux screws up horribly ATM - Issue coming soon...)
lmbelo commentedon Aug 21, 2022
You need to get extra attention when targeting installed Python versions on Linux. You might hit across known issues reported before here. Have a look in all closed issues when you're going afters Linux errors.
Only for the sake of getting you ahead, you can't use apt installed Python working with the DelphiFMX library.
We need a Python executable linked statically or dynamically with the interpreter shared library, or an executable that exposes interpreter's symbols some how. The is not the case of Python available by apt.
peardox commentedon Aug 21, 2022
I was using an Embedded Python 3.9 - which is why the issue is listed here
lmbelo commentedon Aug 21, 2022
Our embeddables should work fine on any of the provided platforms.
viniciusfbb commentedon Aug 21, 2022
@lmbelo, you didn't understand my post. The P4D-Data-Sciences code is wrong and python4delphi's MaskFPUExceptions implementation should be changed as well. I will explain it better.
MaskFPUExceptions code from python4delphi:
python4delphi/Source/PythonEngine.pas
Lines 9288 to 9306 in 19192ad
Note that the code above arbitrarily changes the exception mask to:
[exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]when the ExceptionsMasked parameter is True[exDenormalized, exUnderflow, exPrecision]when the ExceptionsMasked parameter is FalseNow, look at one of the P4D-Data-Sciences import code:
As it stands, if the exception mask before the code above is empty [], after import it will be
[exDenormalized, exUnderflow, exPrecision]. Likewise, if the exception mask isexAllArithmeticExceptions(all exceptions), which is what happens when using OpenGL, D2D, GDIP, Skia, among others, after import the mask will be[exDenormalized, exUnderflow, exPrecision]. And there is no way to predict the mask that will be configured in the application when it arrives at the P4D-Data-Sciences import code, there are many variants, there are several other libraries that change like the Delphi XML lib itself, and it can even vary according to the application (VCL or FMX).So, the correct thing is: save the mask value, change it and after importing the P4D-Data-Sciences, restore the previous mask value and not arbitrarily change it to
[exDenormalized, exUnderflow, exPrecision].I made a possible fix for both codes. First for python4delphi's MaskFPUExceptions:
And now one of the P4D-Data-Sciences import codes:
lmbelo commentedon Aug 21, 2022
@viniciusfbb does Skia changes the exception mask for the whole app life cycle?
peardox commentedon Aug 21, 2022
Just to complicate things ARM can emulate the FPU mask and Mac can be x64 or aarch64
viniciusfbb commentedon Aug 21, 2022
Yes! OpenGL too.
4 remaining items
lmbelo commentedon Aug 21, 2022
Well, regarding Windows x86 (made by FLDCW and FSTCW ???) and x64, the FPSCR flag equivalent is saved across thread context switches. I'm almost sure ARM has a similar approach, I think they save it in the _FPSCR array (I need to confirm this).
Those masks we are setting up on P4D are the ones required by Python.
viniciusfbb commentedon Aug 21, 2022
Not quite. Change the main, run a thread and you will see the value of the mask of the main in the thread... Until a few years ago SetExceptionMask was not even thread safe, but I believe this has already been fixed.
Anyway, just changing the import of P4D-Data-Sciences the way you suggest is not the way I think is the most correct but it will hardly cause problems.
lmbelo commentedon Aug 21, 2022
Well, just tried it for the sake of testing. Definitely changing it on a second thread doesn't affect the main thread. It proves that it is saved across thread context switches.
Still not sure that doing that change is the way to go.
lmbelo commentedon Aug 21, 2022
As did by FMX.Canvas.GDIP, changes made on masks are done per invocation and undone after it. Changing exception masks for app life cycle can be risk.
viniciusfbb commentedon Aug 21, 2022
I didn't say that changes to background threads would affect the main thread's exception mask. In my view, threads being created in the background would have the initial values of exception mask of the main thread.
However, I have confirmed it through testing, and it is not true. New threads copy the values of mask exceptions from the thread that created it, not from the main thread. So your change is safe too.
lmbelo commentedon Aug 21, 2022
@viniciusfbb let me see where Skia is changing exception masks. By now, let's stand to the adjustments in the DSC components only.
peardox commentedon Aug 21, 2022
An important concern is what happens when you call python outside P4D e.g. I'm using my hack above like this
I'm guessing I should also wrap e.g.
MainModule.delphi_style();As well.
MainModule.delphi_train(); can take a few hours to run (and will be using Skia once a second for reporting) - I'll have to give that a go this week
lmbelo commentedon Aug 21, 2022
That's why exception masks should be changed and restored per invocation.
lmbelo commentedon Aug 21, 2022
Btw, how are you running a blocking task on main thread and using Skia to report it out of a second thread?
peardox commentedon Aug 21, 2022
What's exacrly what SafeMaskFPUExceptions() does.
viniciusfbb commentedon Aug 21, 2022
There are numerous operations that depend on masks in exceptions, which are executed every frame of the app's rendering, so we decided to change it already on load.
https://github.com/skia4delphi/skia4delphi/blob/f61e4cc05842e5c8c6f7f6ce340ea2df149032c8/Source/Skia.API.pas#L2103
peardox commentedon Aug 21, 2022
Just starting a new thread for the module call then hand back to Delphi
Yesterday they finally told us they have their own task manager (but it'll also be setting the mask to bad values I guess)
lmbelo commentedon Aug 21, 2022
I understand. My concern is that it would affect other libraries out of our sight.
lmbelo commentedon Aug 21, 2022
You must be careful while using the Python packages out of the main thread.
peardox commentedon Aug 21, 2022
This seems to work...
It uses a Skia shader while the thread is running showing data reported back from that thread (it's a boring progress display)