Description
The code to perform loss of precision checking in parser::parse_internal() for lexer::token_type::value_number types can cause unhandled floating point exceptions with certain combinations of hardware, compilation options and input data. Specifically the following line (currently found at line 6772) causes the exception:
// check if conversion loses precision
const auto int_val = static_cast<number_integer_t>(float_val);
The exception can be generated on Intel x64 processors if the following three conditions occur:
- The floating point unit is configured with the 'floating-point invalid' exception unmasked (enabled).
- The compiler uses a CVTTSD2SI (Convert Scalar Double-Precision Floating-Point Value to Signed Doubleword Integer with Truncation) instruction.
- The value of float_val exceeds that which can be represented (after truncation) in a signed 64 bit integer.
These conditions were found to occur in a DLL that was built using Visual Studio 2015 (Update 1 version 14.0.24720.00) that uses the JSON library. Interestingly it did NOT occur when the DLL was dynamically loaded in a test app that was built to test the DLL (built using the same compiler) but it did occur when the DLL was dynamically loaded by a commercial program (built separately and for which the source code and build options are not available). Both the test app and the commercial program were presented with identical data. This suggests that Visual Studio generates an executable that disables this floating point exception but that for whatever reason the commercial program does not.
A workaround is to manually disable the floating-point invalid exception using, for example, _controlfp_s() (or feenableexcept() under Linux), prior to calling the parse function and then restoring its state afterwards.
It may be better for the library to handle this automatically. This could be done by either:
- Disabling the exception by calling _controlfp_s()/feenableexcept(), probably on entry to, and exit from, parse().
- Preventing the exception by checking the magnitude of float_val before executing the above line to ensure it is capable of fitting in a signed 64 bit integer and if not, keep it as a float_val as is already done in the event of loss of precision.
- Implementing some other means of checking for loss of precision that does not result in a CVTTSD2SI instruction.
If the decision were taken to leave it to the library user to implement their own controls then this should be clearly spelled out in the documentation as this behavior does not seem to be well known and is definitely not expected.