[Bug 46842] C# double.TryParse( String.Empty, out number) succeeds with .NET Framework 4.0 when it should not

wine-bugs at winehq.org wine-bugs at winehq.org
Sat Mar 16 08:42:37 CDT 2019


https://bugs.winehq.org/show_bug.cgi?id=46842

Anastasius Focht <focht at gmx.net> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
     Ever confirmed|0                           |1
             Status|UNCONFIRMED                 |NEW
          Component|-unknown                    |kernel32
           Keywords|                            |source

--- Comment #10 from Anastasius Focht <focht at gmx.net> ---
Hello IanS,

confirming now - so I can sleep well again ;-)

The culprit seems to be .NET Framework 4.x and Windows version set to at least
'Windows 7' (default in new WINEPREFIXes). If you change it to Windows Vista or
lower (Windows XP) it works.

My WINEPREFIX was kept at 'Windows XP' due to winetricks recipe which is not
necessary anymore. .NET Framework 4.x CLR uses different ways on Windows 7+ to
determine various culture/localization information (GetLocaleInfoEx vs.
registry)

Suspecting this, I found some example code to test this here:

https://docs.microsoft.com/en-us/dotnet/api/system.globalization.numberformatinfo.currentinfo?view=netframework-4.7.2

I adapted it to build with lower .NET Frameworks/SDK and get rid of unneeded
stuff:

'numberformatinfo.cs'

--- snip ---
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;

namespace Example
{
    public class NumberFormatInfoExample
    {
        public static void Main()
        {
            NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
            Console.WriteLine("Properties of NumberFormat.CurrentInfo
object:");
            PropertyInfo[] props =
nfi.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            foreach (PropertyInfo prop in props)
            {
                if (prop.PropertyType.IsArray)
                {
                    Array arr = prop.GetValue(nfi, null) as Array;
                    Console.Write(String.Format("   {0}: ", prop.Name) + "{ ");
                    int ctr = 0;
                    foreach (object item in arr)
                    {
                        Console.Write("{0}{1}", item, ctr == arr.Length - 1 ?"
}" : ", ");
                        ctr++;
                    }
                    Console.WriteLine();
                }
                else
                {
                    Console.WriteLine("   {0}: {1}", prop.Name,
prop.GetValue(nfi, null));
                }
            }
        }
    }
}
--- snip ---

You can use LC_ALL to override/check for specific locales.

Running the test app with 'Windows XP' setting:

--- snip ---
$ LC_ALL="en_US.utf8" wine ./numberformatinfo.exe

Properties of NumberFormat.CurrentInfo object:
   CurrencyDecimalDigits: 2
   CurrencyDecimalSeparator: .
   IsReadOnly: True
   CurrencyGroupSizes: { 3 }
   NumberGroupSizes: { 3 }
   PercentGroupSizes: { 3 }
   CurrencyGroupSeparator: ,
   CurrencySymbol: $
   NaNSymbol: NaN
   CurrencyNegativePattern: 0
   NumberNegativePattern: 1
   PercentPositivePattern: 0
   PercentNegativePattern: 0
   NegativeInfinitySymbol: -Infinity
   NegativeSign: -
   NumberDecimalDigits: 2
   NumberDecimalSeparator: .
   NumberGroupSeparator: ,
   CurrencyPositivePattern: 0
   PositiveInfinitySymbol: Infinity
   PositiveSign: +
   PercentDecimalDigits: 2
   PercentDecimalSeparator: .
   PercentGroupSeparator: ,
   PercentSymbol: %
   PerMilleSymbol: %
   NativeDigits: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
   DigitSubstitution: None
--- snip ---

With 'Windows 7' setting:

--- snip ---
$ LC_ALL="en_US.utf8" wine ./numberformatinfo.exe

Properties of NumberFormat.CurrentInfo object:
   CurrencyDecimalDigits: 2
   CurrencyDecimalSeparator: ,
   IsReadOnly: True
   CurrencyGroupSizes: { 3 }
   NumberGroupSizes: { 3 }
   PercentGroupSizes: { 3 }
   CurrencyGroupSeparator: .
   CurrencySymbol: ?
   NaNSymbol: NaN
   CurrencyNegativePattern: 8
   NumberNegativePattern: 1
   PercentPositivePattern: 0
   PercentNegativePattern: 0
   NegativeInfinitySymbol: 
   NegativeSign: -
   NumberDecimalDigits: 2
   NumberDecimalSeparator: ,
   NumberGroupSeparator: .
   CurrencyPositivePattern: 3
   PositiveInfinitySymbol: 
   PositiveSign: +
   PercentDecimalDigits: 2
   PercentDecimalSeparator: ,
   PercentGroupSeparator: .
   PercentSymbol: 
   PerMilleSymbol: 
   NativeDigits: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
   DigitSubstitution: None
--- snip ---

There are various number format properties missing/incorrect.

For reference .NET Core:

https://github.com/dotnet/corefx/blob/1f7c6362d3ad7cebeb748aabfc7e8303b703ed48/src/Common/src/CoreLib/System/Globalization/CultureData.cs

https://github.com/dotnet/corefx/blob/1f7c6362d3ad7cebeb748aabfc7e8303b703ed48/src/Common/src/CoreLib/System/Globalization/CultureData.cs#L2327

.NET number parsing code relies on it.

https://github.com/dotnet/corefx/blob/v2.2.3/src/Common/src/CoreLib/System/Number.Parsing.cs#L399

I also did some debugging of JIT code using cordbg - not for the faint hearted
;-) Just for reference, the simple C# example app already shows it.

--- snip ---
$ wine "c:\\Program Files\\Debugging Tools for Windows (x86)\\cdb.exe"
./bug46842.exe
...
0:000> sxe ld clrjit

0:000> g

0:000> .loadby sos clr

0:000> !name2ee *!System.Double.TryParse

Module:      79881000
Assembly:    mscorlib.dll
Token:       06000b97
MethodDesc:  798f5440
Name:        System.Double.TryParse(System.String, Double ByRef)
JITTED Code Address: 79b22cb0
-----------------------
Token:       06000b98
MethodDesc:  798f544c
Name:        System.Double.TryParse(System.String,
System.Globalization.NumberStyles, System.IFormatProvider, Double ByRef)
JITTED Code Address: 79ac767c
-----------------------
Token:       06000bab
MethodDesc:  798f55ac
Name:        System.Double.TryParse(System.String,
System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo,
Double ByRef)
JITTED Code Address: 79ac76b4
--------------------------------------
Module:      00372e9c
Assembly:    bug46842.exe

0:000> bp 79b22cb0

0:000> g
...

0:000> g
Unable to parse '123AE6'.
Breakpoint 0 hit

0:000> g
Unable to parse ''.
Breakpoint 0 hit

...
0:000> l+t

...
0:000> t

...

0:000> !IP2MD @eip
MethodDesc:   799b71d8
Method Name:  System.Number.ParseNumber(Char* ByRef,
System.Globalization.NumberStyles, NumberBuffer ByRef,
System.Text.StringBuilder, System.Globalization.NumberFormatInfo, Boolean)
Class:        798dc1e4
MethodTable:  79ba6a48
mdToken:      06000e20
Module:       79881000
IsJitted:     yes
CodeAddr:     79ac5db0
Transparency: Critical

0:000> !clrstack
OS Thread Id: 0x30 (0)
Child SP IP       Call Site
0032f1a0 79ac5db0 System.Number.ParseNumber(Char* ByRef,
System.Globalization.NumberStyles, NumberBuffer ByRef,
System.Text.StringBuilder, System.Globalization.NumberFormatInfo, Boolean)
0032f1b4 79ac6394 System.Number.TryStringToNumber(System.String,
System.Globalization.NumberStyles, NumberBuffer ByRef,
System.Text.StringBuilder, System.Globalization.NumberFormatInfo, Boolean)
0032f1e0 79b11f12 System.Number.TryParseDouble(System.String,
System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo,
Double ByRef)
0032f2a4 79ac76d3 System.Double.TryParse(System.String,
System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo,
Double ByRef)
0032f2c0 79b22ccc System.Double.TryParse(System.String, Double ByRef)
0032f2d0 003b0182 Example.Main()
0032f538 791421db [GCFrame: 0032f538] 

0:000> !DumpIL 799b71d8
ilAddr = 79c1fd28
IL_0000: ldarg.2 
IL_0001: ldc.i4.0 
IL_0002: stfld NumberBuffer::scale
IL_0007: ldarg.2 
IL_0008: ldc.i4.0 
IL_0009: stfld NumberBuffer::sign
IL_000e: ldnull 
IL_000f: stloc.2 
IL_0010: ldnull 
IL_0011: stloc.3 
IL_0012: ldnull 
IL_0013: stloc.s VAR OR ARG 4
IL_0015: ldnull 
IL_0016: stloc.s VAR OR ARG 5
IL_0018: ldc.i4.0 
IL_0019: stloc.s VAR OR ARG 6
IL_001b: ldarg.1 
IL_001c: ldc.i4 256
IL_0021: and 
IL_0022: brfalse.s IL_0064
IL_0024: ldarg.s VAR OR ARG 4
IL_0026: callvirt System.Globalization.NumberFormatInfo::get_CurrencySymbol
IL_002b: stloc.2 
IL_002c: ldarg.s VAR OR ARG 4
IL_002e: ldfld System.Globalization.NumberFormatInfo::ansiCurrencySymbol
IL_0033: brfalse.s IL_003d
IL_0035: ldarg.s VAR OR ARG 4
IL_0037: ldfld System.Globalization.NumberFormatInfo::ansiCurrencySymbol
IL_003c: stloc.3 
IL_003d: ldarg.s VAR OR ARG 4
IL_003f: callvirt
System.Globalization.NumberFormatInfo::get_NumberDecimalSeparator
IL_0044: stloc.s VAR OR ARG 4
IL_0046: ldarg.s VAR OR ARG 4
IL_0048: callvirt
System.Globalization.NumberFormatInfo::get_NumberGroupSeparator
IL_004d: stloc.s VAR OR ARG 5
IL_004f: ldarg.s VAR OR ARG 4
IL_0051: callvirt
System.Globalization.NumberFormatInfo::get_CurrencyDecimalSeparator
IL_0056: stloc.0 
IL_0057: ldarg.s VAR OR ARG 4
IL_0059: callvirt
System.Globalization.NumberFormatInfo::get_CurrencyGroupSeparator
IL_005e: stloc.1 
IL_005f: ldc.i4.1 
IL_0060: stloc.s VAR OR ARG 6
IL_0062: br.s IL_0074
IL_0064: ldarg.s VAR OR ARG 4
IL_0066: callvirt
System.Globalization.NumberFormatInfo::get_NumberDecimalSeparator
IL_006b: stloc.0 
IL_006c: ldarg.s VAR OR ARG 4
IL_006e: callvirt
System.Globalization.NumberFormatInfo::get_NumberGroupSeparator
IL_0073: stloc.1 
IL_0074: ldc.i4.0 
IL_0075: stloc.s VAR OR ARG 7
IL_0077: ldc.i4.0 
IL_0078: stloc.s VAR OR ARG 8

...

0:000> !clrstack -a
OS Thread Id: 0x30 (0)
Child SP IP       Call Site
0032f150 79ac6260 System.Number.MatchChars(Char*, System.String)
    PARAMETERS:
        p (<CLR reg>) = 0x00c91230
        str (<CLR reg>) = 0x00c91748
    LOCALS:
        <no data>
        <no data>
        <no data>

0032f154 79ac5e7d System.Number.ParseNumber(Char* ByRef,
System.Globalization.NumberStyles, NumberBuffer ByRef,
System.Text.StringBuilder, System.Globalization.NumberFormatInfo, Boolean)
    PARAMETERS:
        str (0x0032f16c) = 0x0032f1b8
        options (0x0032f18c) = 0x000000e7
        number (0x0032f1b0) = 0x0032f270
        sb (0x0032f1ac) = 0x00000000
        numfmt (0x0032f1a8) = 0x00c9b584
        parseDecimal (0x0032f1a4) = 0x00000000
    LOCALS:
        0x0032f168 = 0x00c9b6a0
        0x0032f164 = 0x00c9b6b0
        0x0032f160 = 0x00000000
        0x0032f15c = 0x00000000
        0x0032f158 = 0x00000000
        0x0032f154 = 0x00000000
        0x0032f188 = 0x00000000
        <CLR reg> = 0x00000000
        <CLR reg> = 0x00000001
        0x0032f184 = 0x00000000
        0x0032f180 = 0x00000000
        <no data>
        0x0032f17c = 0x00c91230
        <CLR reg> = 0x00000000
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>
        <no data>

...

0:000> !do 0x00c91748
Name:        System.String
MethodTable: 79b9f9ac
EEClass:     798d8bb0
Size:        16(0x10) bytes
File:       
C:\windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:      +
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79ba2978  40000ed        4         System.Int32  1 instance        1
m_stringLength
79ba1dc8  40000ee        8          System.Char  1 instance       2b
m_firstChar
79b9f9ac  40000ef        8        System.String  0   shared   static Empty
    >> Domain:Value  001430c0:00c91228 <<
--- snip ---

$ wine --version
wine-4.4

Regards

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.



More information about the wine-bugs mailing list