Skip to content

[Xamarin.Android.Tools.AndroidSdk] Attributes can be null! #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 25, 2022

Conversation

jonpryor
Copy link
Contributor

@jonpryor jonpryor commented Feb 25, 2022

Context: #157
Context: 2d3690e

Commit 2d3690e added Nullable Reference Type support to
Xamarin.Android.Tools.AndroidSdk.dll. Unfortunately, it was
largely "surface level", updating public API, but not all method
implementations were appropriately updated.

Case in point: if $HOME/.config/xbuild/monodroid-config.xml
contains no value in <java-sdk/>, e.g.

<monodroid>
  <android-sdk path="/path/to/android/sdk" />
  <java-sdk /> <!-- empty! -->
</monodroid>

then Visual Studio for Mac may report a first-chance exception
(only reported when debugging Visual Studio for Mac, as the exception
is caught internally):

TODO: exception + stack trace

A major reason to adopt Nullable Reference Types is to prevent the
occurrence of NullReferenceExceptions, so what went wrong?

What went wrong with 2d3690e is that when XML attributes don't
exist, XElement.Attribute() will return null, and most of
our XElement.Attribute() invocations cast the XAttribute return
value to string, "asserting" that a non-null is returned.

Review the codebase for all XElement.Attribute() invocations, and
update all casts from (string) to instead cast to (string?).
This ensures that we don't circumvent the C# compilers Nullable
Reference Type checks, catches the circumvention which was present
in JdkLocations.GetUnixConfiguredJdkPaths(), and thus avoids the
first-chance exception that VSMac could see.

Co-Authored by: @KirillOsenkov

Context: dotnet#157
Context: 2d3690e

Commit 2d3690e added Nullable Reference Type support to
`Xamarin.Android.Tools.AndroidSdk.dll`.  Unfortunately, it was
largely "surface level", updating *public* API, but not all method
implementations were appropriately updated.

Case in point: if `$HOME/.config/xbuild/monodroid-config.xml`
contains *no* value in `<java-sdk/>`, e.g.

	<monodroid>
	  <android-sdk path="/path/to/android/sdk" />
	  <java-sdk /> <!-- empty! -->
	</monodroid>

then Visual Studio for Mac may report a first-chance exception
(only reported when debugging Visual Studio for Mac, as the exception
is caught internally):

	TODO: exception + stack trace

A major reason to adopt Nullable Reference Types is to *prevent* the
occurrence of `NullReferenceException`s, so what went wrong?

What went wrong with 2d3690e is that when XML attributes don't
exist, [`XElement.Attribute()`][0] will return `null`, and most of
our `XElement.Attribute()` invocations cast the `XAttribute` return
value to `string`, "asserting" that a *non-`null`* is returned.

Review the codebase for all `XElement.Attribute()` invocations, and
update all casts from `(string)` to instead cast to `(string?)`.
This ensures that we don't circumvent the C# compilers Nullable
Reference Type checks, catches the circumvention which was present
in `JdkLocations.GetUnixConfiguredJdkPaths()`, and thus avoids the
first-chance exception that VSMac could see.

[0]: https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xelement.attribute?view=net-6.0

Co-Authored by: @KirillOsenkov
@jonpryor jonpryor force-pushed the jonp-xml-attributs-are-null branch from c5f5575 to 30b0b2c Compare February 25, 2022 17:40
@KirillOsenkov
Copy link
Member

The exception we see in VSMac is:

{System.ArgumentNullException: Value cannot be null. (Parameter 'homePath')
   at Xamarin.Android.Tools.JdkInfo..ctor(String homePath, String locator, Action`2 logger) in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs:line 55}

Full stack:

Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.JdkInfo Line 55 in /Users/kirill/uitools/Designer/Xamarin.Designer.Android/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs:55
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.JdkInfo Line 99 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs:99
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.TryGetJdkInfo Line 347 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs:347
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkLocations.GetUnixPreferredJdks.AnonymousMethod__0 Line 14 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs:14
System.Linq.dll!System.Linq.Enumerable.SelectEnumerableIterator<string, Xamarin.Android.Tools.JdkInfo>.MoveNext in :0
System.Linq.dll!System.Linq.Enumerable.WhereSelectEnumerableIterator<Xamarin.Android.Tools.JdkInfo, Xamarin.Android.Tools.JdkInfo>.ToArray in :0
System.Linq.dll!System.Linq.Buffer<Xamarin.Android.Tools.JdkInfo>.Buffer in :0
System.Linq.dll!System.Linq.OrderedEnumerable<Xamarin.Android.Tools.JdkInfo>.GetEnumerator in :0
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkLocations.GetPreferredJdks Line 19 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.cs:19
System.Linq.dll!System.Linq.Enumerable.ConcatIterator<Xamarin.Android.Tools.JdkInfo>.MoveNext in :0
System.Linq.dll!System.Linq.Enumerable.SelectEnumerableIterator<Xamarin.Android.Tools.JdkInfo, string>.MoveNext in :0
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkBase.GetValidPath Line 112 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs:112
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkBase.Initialize Line 69 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs:69
Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkInfo Line 21 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs:21
Xamarin.AndroidTools.Ide.dll!Xamarin.AndroidTools.AndroidSdk.Refresh Line 53 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/Xamarin.AndroidTools/AndroidSdk.cs:53
Xamarin.AndroidTools.Ide.dll!Xamarin.AndroidTools.AndroidSdk.Refresh Line 48 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/Xamarin.AndroidTools/AndroidSdk.cs:48
Xamarin.AndroidTools.Ide.dll!Xamarin.AndroidTools.AndroidSdk.AndroidSdk Line 41 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/Xamarin.AndroidTools/AndroidSdk.cs:41
[Native to Managed Transition] in :0
[Managed to Native Transition] in :0
Xamarin.AndroidTools.Ide.dll!Xamarin.AndroidTools.AndroidSdk.AndroidSdkPath.get Line 142 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/external/androidtools/Xamarin.AndroidTools/AndroidSdk.cs:142
MonoDevelop.MonoDroid.dll!MonoDevelop.MonoDroid.MonoDroidFramework.UpdateSdkLocations Line 119 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/MonoDevelop.MonoDroid/MonoDroidFramework.cs:119
MonoDevelop.MonoDroid.dll!MonoDevelop.MonoDroid.MonoDroidFramework.MonoDroidFramework Line 64 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/MonoDevelop.MonoDroid/MonoDroidFramework.cs:64
[Native to Managed Transition] in :0
[Managed to Native Transition] in :0
MonoDevelop.MonoDroid.dll!MonoDevelop.MonoDroid.MonoDroidFramework.IsInstalled.get Line 82 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/MonoDevelop.MonoDroid/MonoDroidFramework.cs:82
MonoDevelop.MonoDroid.dll!MonoDevelop.MonoDroid.MonoDroidInstalledCondition.Evaluate Line 374 in /Users/runner/work/1/s/MonoDevelop.MonoDroid/MonoDevelop.MonoDroid/MonoDroidFramework.cs:374
Mono.Addins.dll!Mono.Addins.Condition.Evaluate in :0
Mono.Addins.dll!Mono.Addins.TreeNode.IsEnabled.get in :0
Mono.Addins.dll!Mono.Addins.ExtensionNode.ChildNodes.get in :0
Mono.Addins.dll!Mono.Addins.ExtensionNode.ExtensionNodeChanged.add in :0
Mono.Addins.dll!Mono.Addins.ExtensionContext.AddExtensionNodeHandler in :0
Mono.Addins.dll!Mono.Addins.AddinManager.AddExtensionNodeHandler in :0
MonoDevelop.Core.dll!MonoDevelop.Core.Execution.ProcessService.ProcessService Line 82 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/ProcessService.cs:82
MonoDevelop.Core.dll!MonoDevelop.Core.Runtime.ProcessService.get Line 356 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs:356
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.MonoRuntimeInfo.InternalInitialize Line 135 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoRuntimeInfo.cs:135
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.MonoRuntimeInfo.Initialize Line 107 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoRuntimeInfo.cs:107
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.MonoRuntimeInfo.MonoRuntimeInfo Line 56 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoRuntimeInfo.cs:56
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.MonoRuntimeInfo.CreateDummyCurrentRuntime Line 262 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoRuntimeInfo.cs:262
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.MonoTargetRuntimeFactory.CreateRuntimes Line 84 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntimeFactory.cs:84
MonoDevelop.Core.dll!MonoDevelop.Core.Assemblies.SystemAssemblyService.Initialize Line 63 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs:63
MonoDevelop.Core.dll!MonoDevelop.Core.Runtime.Initialize Line 171 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs:171
MonoDevelop.Ide.dll!MonoDevelop.Ide.IdeStartup.Run Line 173 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs:173
MonoDevelop.Ide.dll!MonoDevelop.Ide.IdeStartup.Main Line 754 in /Users/kirill/vsmac/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs:754
VisualStudio.dll!VisualStudioMac.AppDelegate.DidFinishLaunching Line 120 in /Users/kirill/vsmac/main/src/core/VisualStudio/AppDelegate.cs:120
[Native to Managed Transition] in :0
[Managed to Native Transition] in :0
System.Private.CoreLib.dll!System.Reflection.RuntimeMethodInfo.Invoke in :0
Xamarin.Mac.dll!ObjCRuntime.Runtime.InvokeMethod Line 641 in /Users/builder/azdo/_work/1/s/xamarin-macios/src/ObjCRuntime/Runtime.CoreCLR.cs:641
Xamarin.Mac.dll!ObjCRuntime.Runtime.InvokeMethod Line 538 in /Users/builder/azdo/_work/1/s/xamarin-macios/src/ObjCRuntime/Runtime.CoreCLR.cs:538
Xamarin.Mac.dll!ObjCRuntime.Runtime.bridge_runtime_invoke_method Line 1206 in /Users/builder/azdo/_work/1/s/xamarin-macios/runtime/Delegates.generated.cs:1206
[Native to Managed Transition] in :0
[Managed to Native Transition] in :0
Xamarin.Mac.dll!AppKit.NSApplication.Main Line 121 in /Users/builder/azdo/_work/1/s/xamarin-macios/src/AppKit/NSApplication.cs:121
VisualStudio.dll!VisualStudioMac.MainClass.Main Line 38 in /Users/kirill/vsmac/main/src/core/VisualStudio/Main.cs:38

@KirillOsenkov
Copy link
Member

Thanks for doing the proper fix here.

var path = (string) java_sdk.Attribute ("path");
yield return path;
var path = (string?) java_sdk.Attribute ("path");
if (path != null && !string.IsNullOrEmpty (path)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the path != null check is redundant, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd think so, but… Different .NET profiles have different NRT metadata, and iirc in .NET Standard 2.0, string.IsNullOrEmpty() does not have [NotNullWhen(false)] on the parameter, which results in warnings if it isn't explicitly checked. :-(

JdkLocations.MacOS.cs(26,19): warning CS8603: Possible null reference return.

@jonpryor jonpryor merged commit f4c44e2 into dotnet:main Feb 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants