dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.05k stars 4.69k forks source link

`int.Parse()` invalid results on x86_64/Android: RadioButton with ControlTemplate crashes on Android in the release mode #104245

Closed holecekp closed 2 months ago

holecekp commented 3 months ago

Description

If RadioButton uses a ControlTemplate, it works on Android in the debug mode. However, running the app in the release mode causes an instant crash when the page with the RadioButtons is displayed.

The crash is caused in the following following exception in MAUI: System.FormatException: Format_InvalidStringWithOffsetAndReason, ??????????????11, Format_ExpectedAsciiDigit" at RadioButton.ContentAsString(). I am

I am including the full Logcat log (I needed to rename the extension of the file to ".json" because uploading ".logcat" files is not supported here. MauiRadioButtonCrash.json

I am including a repro project wich is a simple combination of a new MAUI app using the standard VS template and the XAML for radion buttons from the demo project linked from the official MAUI documantation on RadioButtons.

The issue is somehow similar to https://github.com/dotnet/maui/issues/22377, however in that issue the problem is for iOS only and it is caused by setting the control template for all RadioButtons from styles. There is a workaround - to set the ContentTemplate for each of the RadionButtons directly without a style. Howevere, this workaroud does not work for my issue so I assime they are different (this one is more severe because there is no workaround).

I have tried to update from MAUI 8.0.40 used in the default VS template to the latest 8.0.60 and it did not help. The app still crashes, just this time the same crash is reported in the FontManager.

Steps to Reproduce

  1. Clone the repository https://github.com/holecekp/MauiBugsRadioButton
  2. Run the app on Android emulator in the debug mode. You can see that it works.
  3. Switch to the Release mode and press Ctrl+F5 to run the app in the release mode. A splash screen is displayed for a fraction of second and then the app crashes.

Link to public reproduction project repository

https://github.com/holecekp/MauiBugsRadioButton

Version with bug

8.0.40 SR5

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Tested on Android 14 emulator

Did you find any workaround?

No workaround. The only way is to use RadioButtons with a simple string without any ControlTemplate.

Relevant log output

Logcat shows an exception:

android.runtime.JavaProxyThrowable: System.FormatException: Format_InvalidStringWithOffsetAndReason, ??????????????11, Format_ExpectedAsciiDigit"
              at System.Text.CompositeFormat.Parse(String )
at Microsoft.Extensions.Logging.LogValuesFormatter..ctor(String )                                                                                                       ...
at Microsoft.Maui.Controls.RadioButton.ContentAsString()
Microsoft.Maui.Controls.RadioButton.UpdateSemantics()                                                                                                      
at Microsoft.Maui.Controls.VisualElement.Microsoft.Maui.IView.get_Semantics()
at Microsoft.Maui.Handlers.ViewHandler.MappingSemantics(IViewHandler handler, IView view)
at Microsoft.Maui.Handlers.ViewHandler.MapSemantics(IViewHandler handler, IView view)
...
github-actions[bot] commented 3 months ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

kevinxufei commented 3 months ago

This issue was not repro on Visual Studio 17.11.0 Preview 2.1 (8.0.60 & 8.0.40 & 8.0.10) with sample project(Android14).

holecekp commented 3 months ago

Besides the stable version of VS 17.10.3, I have installed also the latest VS 17.11.0 Preview 2.1. It crashes on both of them. I have found out that it depends on the emulator, which I am using. Android 14 emulator (Pixel 5 based, x86_64) crashes in the release mode. Moreover, Android 12.1 emulator (Small Desktop, x86_64), and Android 9 emulator (Pixel 5 based, x86_64) crashes as well. However, if I launch the app on Android 11 emulator (pixel 7 emulator, x86) it runs in the release mode without crashing.

I have deleted bin and obj folders, cleaned the solution, and reset the Android emulator to factory settings to rule out the possibility that there could be some remains of any other MAUI or Xamarin.Forms apps that would interfere. It did not help. It still crashed with the same exception.

My suspicion is that the issue depends on the processor architecture (working on x86 but crashing on x86_64). Unfortunately, I have tested only on 4 emulators because it takes a lot of time to build and deploy Release version of the MAUI app.

kevinxufei commented 3 months ago

Unfortunately, I can't reproduce this problem in my side. Maybe this issue needs help from others.

jfversluis commented 3 months ago

@jonathanpeppers ?

jonathanpeppers commented 3 months ago

The app doesn't crash for me in Release mode on a Pixel 7.

From your stacktrace, it crashes on this line, right? https://github.com/dotnet/maui/blob/14a080ebb595f929fc2282fd5a94b31b1f870225/src/Controls/src/Core/RadioButton/RadioButton.cs#L653

Since, it's printing a warning, you aren't supposed to put views in a RadioButton? I don't actually know.

I'm also confused by the log message, is it supposed to be using $"" or is it using string.Format() syntax?

I don't think this problem has anything to do with Android.

I think I have more questions than answers, sorry!

holecekp commented 3 months ago

I did some more testing. Just to be sure, I have also updated to the latest MAUI 8.0.61 - no change. The problem seems to be related to x86_64 processor architecture. The app runs well on other processor architectures. Besides a x86_64 emulator (crashes), I have tested also on a x86 emulator (works) and on a Samsung physical phone with ARM64 (works).

I have also tried to turn off AOT and trimming to narrow down what could cause the problem:

  1. If I use the standard VS template for new MAUI projects, the app instantly crashes on x86_64 emulator with the error that I have described before, but it runs on x86 emulator and ARM64 phone (the Debug version runs also on x86_64, only Release version have the issue).

  2. I have turned off AOT. This time the app does not crash and the splashscreen appears, but notning else, the app ends up in an infinite loop. The same messages can be seen repeating in logcat. An interesting message is a warning that is repeated once a while: Unexpected CPU variant for x86: x86_64. Known variants: atom, sandybridge, silvermont, goldmont, goldmont-plus, tremont, kabylake, default Does it mean that MAUI does not support x86_64 Android devices at all?

Besides that the following error can be seen in logcat repeatidly:

FATAL UNHANDLED EXCEPTION: System.ArgumentException: ArgumentException_BufferNotFromPool Arg_ParamName_Name, array
at System.Buffers.SharedArrayPool`1[[System.Char, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Return(Char[] , Boolean )
at System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()
at Android.Runtime.JavaProxyThrowable.Create(Exception )
...
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz)

Because DefaultInterpolatedStringHandler is mentioned, my guess is that there might be a problem with some kinds of string iterpolation.

This behavior is again only on x86_64 emulators. The x86 emulator and Samsung ARM64 physical device works.

  1. Besides AOT, I have turned off trimming, too. This made no difference to the previous case - an infinite loop on x86_64; runs well on x86 and ARM64.

This showed me that it is important not only to test both Debug and Release versions but also different ABIs for the Release version. According to the devices catalog in the Google Play Console the most of the users of my Xamarin.Forms app uses ARM processors. Devices with X86_64 ABI comprise only 0,2 % of the install base. The vast majority of them are various chromebooks. The only X86_64 phone in the install base is OnePlus 10 Pro. So migrating from Xamarin.Forms to the current version of MAUI would probably make the app unusable for these users. Even if it is only a minority, it would be better if migrating to MAUI had no negative effect on any of the current users.

In the repro project, I am using XAML from the official MAUI sample for the RadioButtons (https://github.com/dotnet/maui-samples/blob/main/8.0/UserInterface/Views/RadioButtonDemos/RadioButtonDemos/Views/RadioButtonControlTemplatePage.xaml). I have just removed images to simplify it a bit. So it should be correct. Moreover, I am using RadioButtons with a ControlTemplate in the Xamarin.Forms version in the production for a long time without any problems.

jonathanpeppers commented 3 months ago

Unexpected CPU variant for x86: x86_64. Known variants: atom, sandybridge, silvermont, goldmont, goldmont-plus, tremont, kabylake, default

This message doesn't mean anything, you can ignore it.

MAUI does not support x86_64 Android devices at all?

No, the default emulator is x86_64. It is certainly supported, because no one could do development otherwise?

I have also tried to turn off AOT and trimming

I'm not sure that is the problem here. Maybe don't do this yet?

What I was suggesting, is to try to avoid the LogMessage() call entirely:

.LogWarning("Warning - {RuntimePlatform} does not support View as the {PropertyName} property of RadioButton; the return value of the ToString() method will be displayed instead.", DeviceInfo.Platform, ContentProperty.PropertyName); 

If you changed your RadioButton to use a ControlTemplate, do you get a different error now? Can you update the attached sample.

I can also test on an emulator; maybe I will see the crash myself there.

holecekp commented 3 months ago

Hello. I did some more testing and I have found out what exactly causes it. You were right that the app does not craash when LogWarning is avoided.

The warning generated by the RadioButton is quite confusing because a View as a RadioButton.Content is supported scenario for Android if a ContronTemplate is used (which it is in the repro project). The line in the code where the app crashes uses the ToString() representation RadioButton.Content only for accessibility. A better way in this case would be a warning message that a RadioButton with a ControlTemplate might not by fully accessible in the current version of MAUI , or it would be even better not to generate this warning at all (especially in the release mode) because there is no reasonable way to avoid it and mention this drawback in the documentation instead.

It is quite demanding to find out where does the error message fired by LogWarning goes to. I have found out that it is sent to LoggerProviderDebugView in the Debug mode (this is arranged by "#if DEBUG" in the MauiProgram.cs in the defailt VS template). In the Release mode it however goes to MauiAppBuilder.NullLogger instead which does nothing and just ignores the message.

Before the NullLogger obtains the warning message so that it could throw it away a suprisingly lot of string manipulation is performed. The LogWarning method parses the message and replaces any names in the curly brackets with the number according to its order and remembers the names. So "Warning - {RuntimePlatform} does not support View as the {PropertyName} property of RadioButton; the return value of the ToString() method will be displayed instead." becomes "Warning - {0} does not support View as the {1} property of RadioButton; the return value of the ToString() method will be displayed instead.". This result is sent to CompositeFormat.Parse when .NET 8 or newer is used. The method expects numbers in the curly brackets and is not happy with what it gets and crashes.

After some more testing, I have found out that the issue is even more low-level. The problem is in the int.ToString() method. It works well in the Debug mode. It works well also in the Release mode on x86 emulator (the one that does not crash). But it returns a corrupted string in the Release mode on the x86_64 emulator (those where the RadioButtons crashes). There is a some kind of padding from unprintable characters before the string representation of the number. Because these characters before the number are not printed when they are for example in the Label.Text, it is hard to notice the problem. But it causes the crash, because the expressions in the curly brackets in the warning message should be replaced by their index and because of this flaw in the int.Parse method, there is someting like "{??????????????0}" instead of "{0}". I have no idea why is this happening.

I have logged the value of 2.ToString(), 12.ToString(), and 200.ToString() using the logcat messages. You can find the result in the x86_64 emulator in the image bellow. It can be seen that there are extra characters. The same code produces the correct texts in the Debug mode, or also in the release mode in x86 emulator. IntParse

To reproduce the problem, I have created a separate branch "5IntParse" in the repro project https://github.com/holecekp/MauiBugsRadioButton/tree/5IntParse. The code in this branch just displays the result of ToString() for 2, 12, and 200 in the Labels (it is created so that it would not crash, just print the numbers). Then each of the ToString() results are compared to the expected string representation (for example 2.ToString() == "2"). The results can be seen below.

The expected result looks like this. There are three "OK" Labels showing that the texts equals with the expected value for all three numbers. This is the result from Release mode in the x86 emulator where there is no issue. release_x86

However on the problematic x86_64 emulators where the RadioButtons crashes, the screen looks like this. The ToString() displays seemingly the correct values, but this is just because the additional incorrect characters before the numbers are invisible in the Label.Text. However, it can be seen that they do not equal to the expected values (2.ToString != "2") indicated by three "Incorrect!!!" messages. release_x86_64

Do you have any idea what could make int.Parse to behave so weird on some emulators? I have exported the properties of one of the problematic emulator in Android Studio in case that you would like to reproduce it using as similar emulator as possible. I think however that the key difference is the "x86_64" processor architecture: emulator properties.txt

jonathanpeppers commented 3 months ago

@holecekp I think we move this issue to dotnet/runtime if the underlying problem is int.Parse(), thanks!

tannergooding commented 3 months ago

@holecekp, could you share what those unknown Unicode characters are?

Given this is int.ToString() on x64, I imagine many other apps would be seeing failing scenarios if it were a problem with the core code or RyuJIT. Just int.ToString() is itself relatively simplistic and there's not many opportunities it could get messed up

Given Mono has a SmallNumberCacheLength of 10, compared to RyuJIT's 300, and the failure includes 12 and 200; I'd guess the issue is rather in UInt32ToDecStr_NoSmallNumberCheck, in which case the most likely issue is in whatever Mono is producing from FormattingHelpers.CountDigits. If you were able to debug and breakpoint, my guess is you'd see that its returning some too large value.

This could be representative of an emulator bug or maybe some intrinsic getting mishandled on the Mono side.

holecekp commented 3 months ago

The extra characters have ASCII code 0. I did another test with numbers with various number of digits. I am including the result returned by int.ToString() method (the length of the resulting string and the ASCII codes of the individual characters in it:

Other numeric types have similar problem. I have tested byte.ToString() values 0, 10, 100 and it returns string of length 10, 9, 8.

Long.ToString() behaves in a similar way. In case of 0 it returns the same result as int.ToString() and byte.ToString(). For higher numbers 10, 100, 1000, ... the resulting strings have lengths 18, 17, 16, ... With each correct digit added, two unwanted ASCII 0 characters are removed from the beginning of the string.

The problem is in MAUI in the Release mode only and only if it runs on x86_64 processor architecture emulator. The same code runs well in Debug mode on the same emulator. It runs well also in the Release mode but on x86 emulator. Moreover, I have tested that this is specific to MAUI. The same code in Xamarin.Forms project gives the correct results also in the Release mode on the problematic emulator where the same code placed in MAUI project have the issue.

tannergooding commented 2 months ago

Moving this to Mono as it doesn't reproduce on RyuJIT. It appears to be unique to Mono

lambdageek commented 2 months ago

Doesn't repro with and arm64 android emulator.

Doesn't repro with a mono x86-64 console app (on an osx-arm64 mac via rosetta, anyway):

public class Program
{
    public static unsafe void Main()
    {
        int i = 200;
        Foo(i.ToString(), "200");
    }

    public static void Foo (string s, string e)
    {
        Console.WriteLine (s.Length);
        Console.WriteLine (s == e ? "ok" : "unequal");
        for (int i = 0; i < s.Length; i++)
        {
            int n = (int)s[i];
            Console.WriteLine ($"{i}: 0x{n:x8}");
        }
    }
}
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <PropertyGroup Condition="true">
    <SelfContained>true</SelfContained>
    <UseMonoRuntime>true</UseMonoRuntime>
  </PropertyGroup>

</Project>

setting up an x86-64 android emulator...

lambdageek commented 2 months ago

@holecekp I can't reproduce this with .NET 8.0.7

What I did:

  1. using VS 17.11 Preview 3
  2. installed the 8.0.303 SDK from https://get.dot.net/
  3. dotnet workload install maui
  4. opened https://github.com/holecekp/MauiBugsRadioButton/tree/5IntParse
  5. setup a "Pixel 5 - API 34 (Android 14.0 - API 34)" x86-64 emulator
  6. select Release configuration
  7. Ctrl-F5

App labels show "OK" 3 times

holecekp commented 2 months ago

Thank you for trying to reproduce the isssue. It is a mistery for me why the issue is on my computer only. I have updated everything in the meanwhile (latest VS preview 3, latest MAUI 8.0.70). Still the issue is there. I had a lot of old dotnet SDKs installed (.NET 6, .NET 5). I have unistalled them but this also had no effect on the issue.

I have also tried to uncheck Optimize code checkbox in the project properties for the Release version to make it more similar to Debug version, which works. Unfortunatelly, this did not solved the issue either.

I have created APK and I will try it tomorrow on an x86_64 emulator on a different computer so that I would know if the problem is really in the APK created by VS, or if it just an emulator issue.

tannergooding commented 2 months ago

Are you running on an i7-i9 13000 series Intel processor?

tannergooding commented 2 months ago

If so, you might be encountering https://community.intel.com/t5/Processors/June-2024-Guidance-regarding-Intel-Core-13th-and-14th-Gen-K-KF/m-p/1607807 and may see it fixed by updating your bios, which should acquire the latest microcode patches

holecekp commented 2 months ago

Thank you for the investigation of the issue and for the suggestion what could cause it. The processor in the the computer with the issue is however older (i5-3470). The issue is also completely deterministic. The result is the same every time. If the issue would be caused by processor instabilities I would expect that the behavior would be probably more random.

I have found something interesting. I have created an APK for the repro project. When I install it and run it in the x86_64 emulator manually (drag&drop in the Android Studio] on my computer, there is the mentioned issue. However, when I install and run the same APK on a different computer (12th Gen Intel Core i7-1260P) on an emulator with the same configuration, it works correctly. So it seems that the problem could be in the emulator on my computer (however everything should be up-to-date, I have installed all available updates in the Android SDK Manager).

steveisok commented 2 months ago

@holecekp since we are unable to reproduce, there's not a lot of action we can take. As a result, I'm going to close the issue. If you have any extra info that would help, please feel free to comment and reopen the issue.