diff --git a/.editorconfig b/.editorconfig index 7f4470eb6b8a..3eb5e6966b5e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -117,6 +117,12 @@ csharp_style_unused_value_expression_statement_preference = discard_variable:sil # 'using' directive preferences csharp_using_directive_placement = outside_namespace:suggestion +# nullability checks + +dotnet_style_coalesce_expression = true +dotnet_style_null_propagation = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true + #### C# Formatting Rules #### # New line preferences diff --git a/DOWNLOADS.md b/DOWNLOADS.md index 25c461f29ae4..99b547478c48 100644 --- a/DOWNLOADS.md +++ b/DOWNLOADS.md @@ -2,6 +2,7 @@ | Date | Version | Package | Release Notes | |------|---------|---------|---------------| +|2023/05/17|16.4.0.6|[xamarin.ios-16.4.0.6.pkg](https://download.visualstudio.microsoft.com/download/pr/b4928191-de3d-42cb-a24f-709e5d71626c/ac849f1f644aee58ed4e7a79cb35a14e/xamarin.ios-16.4.0.6.pkg)|| |2023/02/21|16.2.0.5|[xamarin.ios-16.2.0.5.pkg](https://download.visualstudio.microsoft.com/download/pr/5f9ea8f6-0afe-46b3-b8e8-5dee4c2dd14c/b357a2b833ba86598aaff58fc013f06c/xamarin.ios-16.2.0.5.pkg)|| |2023/02/14|16.2.0.2|[xamarin.ios-16.2.0.2.pkg](https://download.visualstudio.microsoft.com/download/pr/91371293-2fe3-4392-b5ca-d4f73b07ce0a/d9eab6784e5e3e8557d2b509645e2b7e/xamarin.ios-16.2.0.2.pkg)|| |2022/12/15|16.1.1.27|[xamarin.ios-16.1.1.27.pkg](https://download.visualstudio.microsoft.com/download/pr/c8bc547f-869e-4d64-a271-f8b3a29ee545/3d8cfa414fa3e4005dd5b9b9d3817aa4/xamarin.ios-16.1.1.27.pkg)|| @@ -172,6 +173,7 @@ | Date | Version | Package | Release Notes | |------|---------|---------|---------------| +|2023/05/17|9.3.0.6|[xamarin.mac-9.3.0.6.pkg](https://download.visualstudio.microsoft.com/download/pr/b4928191-de3d-42cb-a24f-709e5d71626c/586143733de41c62d8b6179d7eeaf3e5/xamarin.mac-9.3.0.6.pkg)|| |2023/02/21|9.1.0.5|[xamarin.mac-9.1.0.5.pkg](https://download.visualstudio.microsoft.com/download/pr/5f9ea8f6-0afe-46b3-b8e8-5dee4c2dd14c/c5c4f99a992a8cc51b2bad3d31ef6b96/xamarin.mac-9.1.0.5.pkg)|| |2023/02/14|9.1.0.2|[xamarin.mac-9.1.0.2.pkg](https://download.visualstudio.microsoft.com/download/pr/91371293-2fe3-4392-b5ca-d4f73b07ce0a/dbf22c9b27f23d1a8b408ae7d93d4b76/xamarin.mac-9.1.0.2.pkg)|| |2022/12/15|9.0.0.27|[xamarin.mac-9.0.0.27.pkg](https://download.visualstudio.microsoft.com/download/pr/c8bc547f-869e-4d64-a271-f8b3a29ee545/4fbfc55453fe36fe5bf4128d6d495911/xamarin.mac-9.0.0.27.pkg)|| diff --git a/msbuild/Makefile b/msbuild/Makefile index 919ed94faa5b..145960e346a8 100644 --- a/msbuild/Makefile +++ b/msbuild/Makefile @@ -439,14 +439,6 @@ DOTNET_TARGETS += \ endif -# watchOS: contains all of the files for iOS as well (for now, we don't need all of them, so this is optimizable) -ifdef INCLUDE_WATCH -DOTNET_TARGETS += \ - $(foreach target,$(DOTNET_SHARED_FILES) ,$(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/iOS/$(notdir $(target))) \ - $(foreach target,$(DOTNET_WATCHOS_FILES) ,$(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/watchOS/$(notdir $(target))) \ - -endif - # macOS ifdef INCLUDE_MAC DOTNET_TARGETS += \ @@ -467,8 +459,6 @@ DOTNET_DIRECTORIES += \ $(DOTNET_DESTDIR)/$(IOS_WINDOWS_NUGET).Sdk/tools/msbuild/iOS \ $(DOTNET_DESTDIR)/$(TVOS_NUGET).Sdk/tools/msbuild/iOS \ $(DOTNET_DESTDIR)/$(TVOS_NUGET).Sdk/tools/msbuild/tvOS \ - $(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/iOS \ - $(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/watchOS \ $(DOTNET_DESTDIR)/$(MACOS_NUGET).Sdk/tools/msbuild/macOS \ $(DOTNET_DESTDIR)/$(MACCATALYST_NUGET).Sdk/tools/msbuild/iOS \ $(DOTNET_DESTDIR)/$(MACCATALYST_NUGET).Sdk/tools/msbuild/MacCatalyst \ @@ -485,12 +475,6 @@ $(DOTNET_DESTDIR)/$(TVOS_NUGET).Sdk/tools/msbuild/%: $(IOS_DESTDIR)$(MONOTOUCH_P $(DOTNET_DESTDIR)/$(TVOS_NUGET).Sdk/tools/msbuild/tvOS/%: $(IOS_DESTDIR)$(MONOTOUCH_PREFIX)/lib/msbuild/TVOS/% | $(DOTNET_DIRECTORIES) $(Q) $(CP) $< $@ -$(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/%: $(IOS_DESTDIR)$(MONOTOUCH_PREFIX)/lib/msbuild/% | $(DOTNET_DIRECTORIES) - $(Q) $(CP) $< $@ - -$(DOTNET_DESTDIR)/$(WATCHOS_NUGET).Sdk/tools/msbuild/watchOS/%: $(IOS_DESTDIR)$(MONOTOUCH_PREFIX)/lib/msbuild/WatchOS/% | $(DOTNET_DIRECTORIES) - $(Q) $(CP) $< $@ - $(DOTNET_DESTDIR)/$(MACCATALYST_NUGET).Sdk/tools/msbuild/%: $(IOS_DESTDIR)$(MONOTOUCH_PREFIX)/lib/msbuild/% | $(DOTNET_DIRECTORIES) $(Q) $(CP) $< $@ diff --git a/msbuild/Xamarin.HotRestart.PreBuilt/Makefile b/msbuild/Xamarin.HotRestart.PreBuilt/Makefile index 7aa162d193a5..bda46c7abcf9 100644 --- a/msbuild/Xamarin.HotRestart.PreBuilt/Makefile +++ b/msbuild/Xamarin.HotRestart.PreBuilt/Makefile @@ -13,7 +13,7 @@ $(TOP)/tests/dotnet/%: $(Q) $(MAKE) -C $(dir $@) $* .build-stamp: $(wildcard Xamarin.PreBuilt.iOS/*) Makefile - $(Q_GEN) $(DOTNET) build Xamarin.PreBuilt.iOS/Xamarin.PreBuilt.iOS.csproj "/bl:$(abspath ./msbuild.binlog)" $(DOTNET_BUILD_VERBOSITY) + $(Q_GEN) if ! $(DOTNET) build Xamarin.PreBuilt.iOS/Xamarin.PreBuilt.iOS.csproj "/bl:$(abspath ./msbuild.binlog)" $(DOTNET_BUILD_VERBOSITY); then echo "Binlog: $(abspath ./msbuild.binlog)"; exit 1; fi $(Q) touch $@ $(DOTNET_DESTDIR)/$(IOS_WINDOWS_NUGET).Sdk/tools/msbuild/iOS/Xamarin.PreBuilt.iOS.app.zip: Xamarin.PreBuilt.iOS.app.zip diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ClassRedirectorTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ClassRedirectorTask.cs deleted file mode 100644 index 799d615dd40a..000000000000 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/ClassRedirectorTask.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using ClassRedirector; - -using Xamarin.Messaging.Build.Client; - -#nullable enable - -namespace Xamarin.MacDev.Tasks { - public class ClassRedirector : XamarinTask, ITaskCallback { - [Required] - public string InputDirectory { get; set; } = string.Empty; - - [Required] - public string OutputDirectory { get; set; } = string.Empty; - - [Required] - public ITaskItem? ClassMapPath { get; set; } - - [Required] - public string PlatformAssembly { get; set; } = string.Empty; - - public override bool Execute () - { - if (ShouldExecuteRemotely ()) { - var taskRunner = new TaskRunner (SessionId, BuildEngine4); - return taskRunner.RunAsync (this).Result; - } - - return ExecuteLocally (); - } - - public IEnumerable GetAdditionalItemsToBeCopied () - { - if (!Directory.Exists (InputDirectory)) - return Enumerable.Empty (); - - return Directory.GetFiles (InputDirectory, "*", SearchOption.AllDirectories) - .Select (f => new TaskItem (f)); - } - - public bool ShouldCopyToBuildServer (ITaskItem item) => true; - - public bool ShouldCreateOutputFile (ITaskItem item) => true; - - bool ExecuteLocally () - { - if (!Directory.Exists (InputDirectory)) { - Log.LogError ($"InputDirectory {InputDirectory} doesn't exist."); - return false; - } - - if (InputDirectory == OutputDirectory) { - Log.LogError ($"OutputDirectory {OutputDirectory} must be difference from InputDirectory."); - return false; - } - - if (!Directory.Exists (OutputDirectory)) { - try { - Directory.CreateDirectory (OutputDirectory); - } catch (Exception directoryException) { - Log.LogErrorFromException (directoryException); - } - } - - if (!DirectoryIsWritable (OutputDirectory)) { - Log.LogError ($"OutputDirectory {OutputDirectory} is not writable."); - return false; - } - - var classMapPath = ClassMapPath!.ItemSpec; - if (!File.Exists (classMapPath)) { - Log.LogError ($"ClassMapPath file {classMapPath} does not exist."); - return false; - } - - var xamarinDll = PlatformAssembly; - - if (!File.Exists (xamarinDll)) - xamarinDll = Path.Combine (InputDirectory, PlatformAssembly); - - if (!File.Exists (xamarinDll)) { - Log.LogError ($"PlatformAssembly {PlatformAssembly} does not exist as is or in {InputDirectory}"); - return false; - } - - - - var dllsToProcess = CollectDlls (InputDirectory); - - var map = ReadRegistrarFile (classMapPath); - - try { - Log.LogMessage (MessageImportance.Low, $"Redirecting class_handle usage from directory {InputDirectory} in the following dlls: {string.Join (",", dllsToProcess)}"); - Log.LogMessage (MessageImportance.Low, $"Redirecting class_handle usage with the platform dll {xamarinDll}"); - Log.LogMessage (MessageImportance.Low, $"Redirecting class_handle usage with the following {nameof (ClassMapPath)}: {classMapPath}"); - var rewriter = new Rewriter (map, xamarinDll, dllsToProcess, OutputDirectory); - rewriter.Process (); - } catch (Exception e) { - Log.LogErrorFromException (e); - return false; - } - - return true; - } - - static bool DirectoryIsWritable (string path) - { - var info = new DirectoryInfo (path); - return !info.Attributes.HasFlag (FileAttributes.ReadOnly); - } - - static string [] CollectDlls (string dir) - { - return Directory.GetFiles (dir, "*.dll"); // GetFiles returns full paths - } - - static CSToObjCMap ReadRegistrarFile (string path) - { - var doc = XDocument.Load (path); - var map = CSToObjCMap.FromXDocument (doc); - if (map is null) - throw new Exception ($"Unable to read static registrar map file {path}"); - return map; - } - } -} - diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index fd654702bedb..15085fc0124e 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -72,12 +72,6 @@ external\SdkVersions.cs - - external\CSToObjCMap.cs - - - external\ObjCNameIndex.cs - diff --git a/src/Foundation/NSUrlSessionHandler.cs b/src/Foundation/NSUrlSessionHandler.cs index e2328637fb43..677ed5a3d42c 100644 --- a/src/Foundation/NSUrlSessionHandler.cs +++ b/src/Foundation/NSUrlSessionHandler.cs @@ -935,9 +935,6 @@ public override void DidCompleteWithError (NSUrlSession session, NSUrlSessionTas // this can happen if the HTTP request times out and it is removed as part of the cancellation process if (inflight is not null) { - // set the stream as finished - inflight.Stream.TrySetReceivedAllData (); - // send the error or send the response back if (error is not null || serverError is not null) { // got an error, cancel the stream operatios before we do anything @@ -948,6 +945,9 @@ public override void DidCompleteWithError (NSUrlSession session, NSUrlSessionTas inflight.CompletionSource.TrySetException (exc); inflight.Stream.TrySetException (exc); } else { + // set the stream as finished + inflight.Stream.TrySetReceivedAllData (); + inflight.Completed = true; SetResponse (inflight); } @@ -1334,8 +1334,10 @@ public override async Task ReadAsync (byte [] buffer, int offset, int count while (current is null) { lock (dataLock) { - if (data.Count == 0 && receivedAllData && position == length) + if (data.Count == 0 && receivedAllData && position == length) { + ThrowIfNeeded (cancellationToken); return 0; + } if (data.Count > 0 && current is null) { current = data.Peek (); diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index f86fc65711d0..ecf29a311442 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -325,6 +325,12 @@ unsafe static void Initialize (InitializationOptions* options) throw ErrorHelper.CreateError (8010, msg); } +#if NET + if (options->RegistrationMap is not null && options->RegistrationMap->map is not null) { + ClassHandles.InitializeClassHandles (options->RegistrationMap->map); + } +#endif + IntPtrEqualityComparer = new IntPtrEqualityComparer (); TypeEqualityComparer = new TypeEqualityComparer (); diff --git a/src/bgen/Generator.cs b/src/bgen/Generator.cs index 33a6c788a629..030be5c084dd 100644 --- a/src/bgen/Generator.cs +++ b/src/bgen/Generator.cs @@ -1269,22 +1269,18 @@ void DeclareInvoker (MethodInfo mi) if (AttributeManager.HasAttribute (mi)) return; - try { - // arm64 never requires stret, so we'll always need the non-stret variants - RegisterMethod (false, mi, MakeSig (mi, false), false); - RegisterMethod (false, mi, MakeSuperSig (mi, false), false); + // arm64 never requires stret, so we'll always need the non-stret variants + RegisterMethod (false, mi, MakeSig (mi, false), false); + RegisterMethod (false, mi, MakeSuperSig (mi, false), false); - if (CheckNeedStret (mi)) { - RegisterMethod (true, mi, MakeSig (mi, true), false); - RegisterMethod (true, mi, MakeSuperSig (mi, true), false); + if (CheckNeedStret (mi)) { + RegisterMethod (true, mi, MakeSig (mi, true), false); + RegisterMethod (true, mi, MakeSuperSig (mi, true), false); - if (AttributeManager.HasAttribute (mi)) { - RegisterMethod (true, mi, MakeSig (mi, true, true), true); - RegisterMethod (true, mi, MakeSuperSig (mi, true, true), true); - } + if (AttributeManager.HasAttribute (mi)) { + RegisterMethod (true, mi, MakeSig (mi, true, true), true); + RegisterMethod (true, mi, MakeSuperSig (mi, true, true), true); } - } catch (BindingException ex) { - throw ex; } } static char [] invalid_selector_chars = new char [] { '*', '^', '(', ')' }; diff --git a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs index f32166f006c0..bdd8792992b9 100644 --- a/tests/monotouch-test/System.Net.Http/MessageHandlers.cs +++ b/tests/monotouch-test/System.Net.Http/MessageHandlers.cs @@ -271,6 +271,133 @@ public void TestNSUrlSessionEphemeralDisabledCookies () } } + [Test] + public void TestNSUrlSessionTimeoutExceptionWhileStreamingContent () + { + if (!HttpListener.IsSupported) { + Assert.Inconclusive ("HttpListener is not supported"); + } + + // HTPP listener config + IPEndPoint httpListenerEndPoint = null; + var serverLaunchedSemaphore = new SemaphoreSlim (0, 1); + const int expectedHttpResponseContentLength = 10; + + var serverCancellationTokenSource = new CancellationTokenSource (); + var serverCancellationToken = serverCancellationTokenSource.Token; + + + // NSUrlSession config + var config = NSUrlSessionConfiguration.DefaultSessionConfiguration; + config.TimeoutIntervalForResource = 3; + + Task.Run (async () => { + // Trying to bing a HttpListener to the first available port + // To avoid race condition, we cannot list available ports, then decide to bind to one of them + HttpListener httpListener = null; + + // IANA suggested range for dynamic or private ports + const int MinPort = 49215; + const int MaxPort = 65535; + + int listeningPort = -1; + for (var port = MinPort; port < MaxPort; port++) { + httpListener = new HttpListener (); + httpListener.Prefixes.Add ($"http://*:{port}/"); + try { + httpListener.Start (); + listeningPort = port; + break; + } catch { + // nothing to do here -- the listener disposes itself when Start throws + } + } + + if (httpListener is null) { + return; + } + + httpListenerEndPoint = new IPEndPoint (IPAddress.Any, listeningPort); + + serverLaunchedSemaphore.Release (); + + try { + while (true) { + var contextTask = httpListener.GetContextAsync (); + Task.WaitAny ( + new Task [] { contextTask }, + serverCancellationToken); + + var context = contextTask.Result; + var request = context.Request; + var response = context.Response; + + // Construct a response. + response.ContentType = "application/octet-stream"; + response.StatusCode = 200; + + try { + // Dripping response blocks, with increasing interval + using (var output = response.OutputStream) { + for (var i = 0; i < expectedHttpResponseContentLength; i++) { + serverCancellationToken.ThrowIfCancellationRequested (); + + await output.WriteAsync (new byte [] { 0x42 }); + output.Flush (); + + await Task.Delay (TimeSpan.FromSeconds (i)); + } + } + } finally { + response.Close (); + } + } + } finally { + httpListener.Stop (); + } + }); + + var timeoutExceptionWasThrown = false; + var timeoutExceptionShouldHaveBeenThrown = true; + + var done = TestRuntime.TryRunAsync (TimeSpan.FromSeconds (30), async () => { + HttpClient client = new HttpClient (new NSUrlSessionHandler (config)); + + await serverLaunchedSemaphore.WaitAsync (); + + try { + var responseMessage = await client.GetAsync ( + $"http://{httpListenerEndPoint.Address}:{httpListenerEndPoint.Port}", + HttpCompletionOption.ResponseHeadersRead); + + using (var contentStream = await responseMessage.Content.ReadAsStreamAsync ()) + using (var outputStream = new global::System.IO.MemoryStream ()) { + await contentStream.CopyToAsync (outputStream); + + timeoutExceptionWasThrown = false; + timeoutExceptionShouldHaveBeenThrown = outputStream.ToArray ().Length < expectedHttpResponseContentLength; + } + } catch (Exception e) + when (e is TimeoutException || e is HttpRequestException) { + timeoutExceptionWasThrown = true; + } + }, out var ex); + + serverCancellationTokenSource.Cancel (); + + if (!done) { + Assert.Inconclusive ("Test run timedout."); + } + + Assert.IsNull (ex, "Exception"); + + if (!timeoutExceptionShouldHaveBeenThrown) { + Assert.Inconclusive ("Failed to produce a timeout. The response content was streamed completely."); + } else { + Assert.IsTrue (timeoutExceptionWasThrown, "Timeout exception is thrown."); + } + } + #endif // ensure that if we have a redirect, we do not have the auth headers in the following requests diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 13d17a99fa7f..2182dea50ef2 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -18,6 +18,8 @@ using ObjCRuntime; +using ClassRedirector; + #if MONOTOUCH using PlatformResolver = MonoTouch.Tuner.MonoTouchResolver; #elif MMP @@ -166,7 +168,6 @@ public bool IsDefaultMarshalManagedExceptionMode { public bool EnableBitCode { get { return BitCodeMode != BitCodeMode.None; } } public bool SkipMarkingNSObjectsInUserAssemblies { get; set; } - public string ClassMapPath = ""; // assembly_build_targets describes what kind of native code each assembly should be compiled into for mobile targets (iOS, tvOS, watchOS). // An assembly can be compiled into: static object (.o), dynamic library (.dylib) or a framework (.framework). @@ -1013,10 +1014,11 @@ public void RunRegistrar () } #endif var registrar = new Registrar.StaticRegistrar (this); - if (RootAssemblies.Count == 1) - registrar.GenerateSingleAssembly (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, Path.GetFileNameWithoutExtension (RootAssembly), out var _, ClassMapPath); - else - registrar.Generate (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, out var _, ClassMapPath); + if (RootAssemblies.Count == 1) { + registrar.GenerateSingleAssembly (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, Path.GetFileNameWithoutExtension (RootAssembly), out var _); + } else { + registrar.Generate (resolver, resolvedAssemblies.Values, Path.ChangeExtension (registrar_m, "h"), registrar_m, out var _); + } } public IEnumerable Abis { diff --git a/tools/common/CSToObjCMap.cs b/tools/common/CSToObjCMap.cs index e98a929dfced..2d3f5cf77c70 100644 --- a/tools/common/CSToObjCMap.cs +++ b/tools/common/CSToObjCMap.cs @@ -7,46 +7,9 @@ namespace ClassRedirector { public class CSToObjCMap : Dictionary { - const string objMapName = "CSToObjCMap"; - const string elementName = "Element"; - const string csNameName = "CSName"; public CSToObjCMap () : base () { } - - public static XElement ToXElement (CSToObjCMap map) - { - return new XElement (objMapName, Elements (map)); - } - - static IEnumerable Elements (CSToObjCMap map) - { - return map.Select (kvp => new XElement (elementName, new XAttribute (csNameName, kvp.Key), ObjCNameIndex.ToXElement (kvp.Value))); - } - - public static CSToObjCMap FromXElement (XElement xmap) - { - var map = new CSToObjCMap (); - var elements = from el in xmap.Descendants (elementName) - select new KeyValuePair (el.Attribute (csNameName)?.Value, - ObjCNameIndex.FromXElement (el.Element (ObjCNameIndex.ObjNameIndexName))); - foreach (var elem in elements) { - if (elem.Key is not null && elem.Value is not null) - map.Add (elem.Key, elem.Value); - } - return map; - } - - public static CSToObjCMap? FromXDocument (XDocument doc) - { - var el = doc.Descendants (objMapName).FirstOrDefault (); - return el is null ? null : FromXElement (el); - } - - public static XDocument ToXDocument (CSToObjCMap map) - { - return new XDocument (ToXElement (map)); - } } } diff --git a/tools/common/Driver.cs b/tools/common/Driver.cs index e2af63788031..a06336644acf 100644 --- a/tools/common/Driver.cs +++ b/tools/common/Driver.cs @@ -257,8 +257,6 @@ static bool ParseOptions (Application app, Mono.Options.OptionSet options, strin options.Add ("skip-marking-nsobjects-in-user-assemblies:", "Don't mark NSObject (and any subclass of NSObject) in user assemblies in the linker. This may break your app, use at own risk.", v => { app.SkipMarkingNSObjectsInUserAssemblies = ParseBool (v, "--skip-marking-nsobjects-in-user-assemblies"); }); - options.Add ("class-map-path=", "Sets the path for an output path to generate a class map XML file used to optimize class handle access when the static registrar has been used.", v => { app.ClassMapPath = v; }); - // Keep the ResponseFileSource option at the end. options.Add (new Mono.Options.ResponseFileSource ()); diff --git a/tools/common/ObjCNameIndex.cs b/tools/common/ObjCNameIndex.cs index cac09237722b..3b26891de46b 100644 --- a/tools/common/ObjCNameIndex.cs +++ b/tools/common/ObjCNameIndex.cs @@ -6,9 +6,6 @@ namespace ClassRedirector { public class ObjCNameIndex { - public const string ObjNameIndexName = "ObjNameIndex"; - const string nameName = "Name"; - const string indexName = "Index"; public ObjCNameIndex (string objCName, int mapIndex) { ObjCName = objCName; @@ -16,24 +13,6 @@ public ObjCNameIndex (string objCName, int mapIndex) } public string ObjCName { get; private set; } public int MapIndex { get; private set; } - - public static XElement ToXElement (ObjCNameIndex nameIndex) - { - return new XElement (ObjNameIndexName, - new XElement (nameName, nameIndex.ObjCName), - new XElement (indexName, nameIndex.MapIndex)); - } - - public static ObjCNameIndex? FromXElement (XElement? objNameIndex) - { - if (objNameIndex is null) - return null; - var name = (string?) objNameIndex.Element (nameName); - var index = (int?) objNameIndex.Element (indexName); - if (name is null || index is null) - return null; - return new ObjCNameIndex (name, index.Value); - } } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/Rewriter.cs b/tools/common/Rewriter.cs similarity index 77% rename from msbuild/Xamarin.MacDev.Tasks/Tasks/Rewriter.cs rename to tools/common/Rewriter.cs index eb573ba1f080..b9cd49713f5c 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/Rewriter.cs +++ b/tools/common/Rewriter.cs @@ -5,10 +5,12 @@ using Mono.Cecil; using Mono.Cecil.Cil; using ClassRedirector; +using Mono.Linker; #nullable enable -namespace Xamarin.MacDev.Tasks { +namespace ClassRedirector { +#if NET public class Rewriter { const string runtimeName = "ObjCRuntime.Runtime"; const string classHandleName = "ObjCRuntime.Runtime/ClassHandles"; @@ -18,35 +20,54 @@ public class Rewriter { const string classPtrName = "class_ptr"; CSToObjCMap map; string pathToXamarinAssembly; - string [] assembliesToPatch; - string outputDirectory; - SimpleAssemblyResolver resolver; + string? outputDirectory = null; Dictionary csTypeToFieldDef = new Dictionary (); + IEnumerable assemblies; + AssemblyDefinition xamarinAssembly; + Xamarin.Tuner.DerivedLinkContext linkContext; - public Rewriter (CSToObjCMap map, string pathToXamarinAssembly, string [] assembliesToPatch, string outputDirectory) + public Rewriter (CSToObjCMap map, IEnumerable assembliesToPatch, Xamarin.Tuner.DerivedLinkContext? linkContext) { this.map = map; - this.pathToXamarinAssembly = pathToXamarinAssembly; - this.assembliesToPatch = assembliesToPatch; - this.outputDirectory = outputDirectory; - resolver = new SimpleAssemblyResolver (assembliesToPatch); + this.assemblies = assembliesToPatch; + var xasm = assembliesToPatch.Select (assem => assem.MainModule).FirstOrDefault (ContainsNativeHandle)?.Assembly; + if (xasm is null) { + throw new Exception ("Unable to find Xamarin assembly."); + } else { + xamarinAssembly = xasm; + pathToXamarinAssembly = xamarinAssembly.MainModule.FileName; + } + if (linkContext is null) { + throw new Exception ("Rewriter needs a valid link context."); + } else { + this.linkContext = linkContext; + } + } - public void Process () + public string Process () { - var classMap = CreateClassHandles (); + Dictionary classMap; + try { + classMap = CreateClassHandles (); + } catch (Exception e) { + // if this throws, no changes are made to the assemblies + // so it's safe to log it on the far side. + return e.Message; + } PatchClassPtrUsage (classMap); + return ""; } Dictionary CreateClassHandles () { var classMap = new Dictionary (); - using var assemblyStm = new FileStream (pathToXamarinAssembly, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); - using var module = ModuleDefinition.ReadModule (assemblyStm); + var module = xamarinAssembly.MainModule; var classHandles = LocateClassHandles (module); - if (classHandles is null) - throw new Exception ($"Unable to find {classHandleName} type in {pathToXamarinAssembly}"); + if (classHandles is null) { + throw new Exception ($"Unable to find {classHandleName} type in Module {module.Name} File {module.FileName}, assembly {xamarinAssembly.Name}"); + } var initMethod = classHandles.Methods.FirstOrDefault (m => m.Name == initClassHandlesName); if (initMethod is null) @@ -56,16 +77,19 @@ Dictionary CreateClassHandles () var mtClassMapDef = LocateMTClassMap (module); if (mtClassMapDef is null) - throw new Exception ($"Unable to find {mtClassMapName} in {pathToXamarinAssembly}"); + throw new Exception ($"Unable to find {mtClassMapName} in Module {module.Name} File {module.FileName}, assembly {xamarinAssembly.Name}"); - var nativeHandle = module.Types.FirstOrDefault (t => t.FullName == nativeHandleName); + var nativeHandle = LocateNativeHandle (module); if (nativeHandle is null) - throw new Exception ($"Unable to find {nativeHandleName} in {pathToXamarinAssembly}"); + throw new Exception ($"Unable to find {nativeHandleName} in Module {module.Name} File {module.FileName}, assembly {xamarinAssembly.Name}"); var nativeHandleOpImplicit = FindOpImplicit (nativeHandle); if (nativeHandleOpImplicit is null) throw new Exception ($"Unable to find implicit cast in {nativeHandleName}"); + if (map.Count () == 0) + return classMap; + foreach (var nameIndexPair in map) { var csName = nameIndexPair.Key; var nameIndex = nameIndexPair.Value; @@ -74,7 +98,7 @@ Dictionary CreateClassHandles () classMap [csName] = fieldDef; } - module.Write (ToOutputFileName (pathToXamarinAssembly)); + MarkForSave (xamarinAssembly); return classMap; } @@ -124,6 +148,16 @@ FieldDefinition AddPublicStaticField (TypeDefinition inType, string fieldName, T return fieldDef; } + bool ContainsNativeHandle (ModuleDefinition module) + { + return LocateNativeHandle (module) is not null; + } + + TypeDefinition? LocateNativeHandle (ModuleDefinition module) + { + return AllTypes (module).FirstOrDefault (t => t.FullName == nativeHandleName); + } + TypeDefinition? LocateClassHandles (ModuleDefinition module) { return AllTypes (module).FirstOrDefault (t => t.FullName == classHandleName); @@ -136,21 +170,28 @@ FieldDefinition AddPublicStaticField (TypeDefinition inType, string fieldName, T void PatchClassPtrUsage (Dictionary classMap) { - foreach (var path in assembliesToPatch) { - using var stm = new FileStream (path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); - using var module = ModuleDefinition.ReadModule (stm); - PatchClassPtrUsage (classMap, module); - module.Write (ToOutputFileName (path)); + foreach (var assem in assemblies) { + var module = assem.MainModule; + if (PatchClassPtrUsage (classMap, module)) { + MarkForSave (assem); + } } } - void PatchClassPtrUsage (Dictionary classMap, ModuleDefinition module) + // returns true if the assembly was changed. + bool PatchClassPtrUsage (Dictionary classMap, ModuleDefinition module) { + var dirty = false; foreach (var cl in AllTypes (module)) { if (classMap.TryGetValue (cl.FullName, out var classPtrField)) { + dirty = true; + // if this doesn't throw, it will + // always change the contents of an + // assembly PatchClassPtrUsage (cl, classPtrField); } } + return dirty; } void PatchClassPtrUsage (TypeDefinition cl, FieldDefinition classPtrField) @@ -291,6 +332,15 @@ string ToOutputFileName (string pathToInputFileName) { return Path.Combine (outputDirectory, Path.GetFileName (pathToInputFileName)); } + + void MarkForSave (AssemblyDefinition assembly) + { + var annotations = linkContext.Annotations; + var action = annotations.GetAction (assembly); + if (action == AssemblyAction.Copy) + annotations.SetAction (assembly, AssemblyAction.Save); + } } +#endif } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 47d8172de8a7..6ce1973a8d9c 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -2843,7 +2843,7 @@ public string GetInitializationMethodName (string single_assembly) } } - void Specialize (AutoIndentStringBuilder sb, out string initialization_method, string type_map_path) + void Specialize (AutoIndentStringBuilder sb, out string initialization_method) { List exceptions = new List (); List skip = new List (); @@ -3295,12 +3295,15 @@ void Specialize (AutoIndentStringBuilder sb, out string initialization_method, s sb.WriteLine (map.ToString ()); sb.WriteLine (map_init.ToString ()); - - if (!string.IsNullOrEmpty (type_map_path)) { - var doc = CSToObjCMap.ToXDocument (map_dict); - doc.Save (type_map_path); +#if NET + if (App.Optimizations.RedirectClassHandles == true) { + var rewriter = new Rewriter (map_dict, GetAssemblies (), LinkContext); + var result = rewriter.Process (); + if (!string.IsNullOrEmpty (result)) { + Driver.Log (5, $"Not redirecting class handles because {result}"); + } } - +#endif ErrorHelper.ThrowIfErrors (exceptions); } @@ -5504,24 +5507,24 @@ public void FilterTrimmedApi (AnnotationStore annotations) } } - public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method, string type_map_path) + public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method) { single_assembly = assembly; - Generate (resolver, assemblies, header_path, source_path, out initialization_method, type_map_path); + Generate (resolver, assemblies, header_path, source_path, out initialization_method); } - public void Generate (IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + public void Generate (IEnumerable assemblies, string header_path, string source_path, out string initialization_method) { - Generate (null, assemblies, header_path, source_path, out initialization_method, type_map_path); + Generate (null, assemblies, header_path, source_path, out initialization_method); } - public void Generate (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) + public void Generate (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, out string initialization_method) { Register (resolver, assemblies); - Generate (header_path, source_path, out initialization_method, type_map_path); + Generate (header_path, source_path, out initialization_method); } - public void Generate (string header_path, string source_path, out string initialization_method, string type_map_path) + public void Generate (string header_path, string source_path, out string initialization_method) { var sb = new AutoIndentStringBuilder (); header = new AutoIndentStringBuilder (); @@ -5556,7 +5559,7 @@ public void Generate (string header_path, string source_path, out string initial if (App.Embeddinator) methods.WriteLine ("void xamarin_embeddinator_initialize ();"); - Specialize (sb, out initialization_method, type_map_path); + Specialize (sb, out initialization_method); methods.WriteLine (); methods.AppendLine (); diff --git a/tools/devops/automation/templates/tests/build.yml b/tools/devops/automation/templates/tests/build.yml index 329120260791..321bbb502e36 100644 --- a/tools/devops/automation/templates/tests/build.yml +++ b/tools/devops/automation/templates/tests/build.yml @@ -204,6 +204,7 @@ steps: set -e $(Build.SourcesDirectory)/xamarin-macios/system-dependencies.sh --provision-simulators --ignore-shellcheck --ignore-yamllint displayName: 'Provision simulators' + timeoutInMinutes: 250 - template: ./run-tests.yml parameters: diff --git a/tools/dotnet-linker/Steps/RegistrarStep.cs b/tools/dotnet-linker/Steps/RegistrarStep.cs index dd6f8795d9a7..53871a4ea253 100644 --- a/tools/dotnet-linker/Steps/RegistrarStep.cs +++ b/tools/dotnet-linker/Steps/RegistrarStep.cs @@ -42,7 +42,7 @@ protected override void TryEndProcess () // so we need to remove those that were later trimmed away by the trimmer. Configuration.Target.StaticRegistrar.FilterTrimmedApi (Annotations); } - Configuration.Target.StaticRegistrar.Generate (header, code, out var initialization_method, app.ClassMapPath); + Configuration.Target.StaticRegistrar.Generate (header, code, out var initialization_method); var items = new List (); foreach (var abi in Configuration.Abis) { diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 756a77b41fb5..213f5d6f1e75 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -107,6 +107,9 @@ tools\common\CSToObjCMap.cs + + tools\common\Rewriter.cs + tools\common\ObjCNameIndex.cs diff --git a/tools/mmp/driver.cs b/tools/mmp/driver.cs index 74a982c0791d..2c8d9768e120 100644 --- a/tools/mmp/driver.cs +++ b/tools/mmp/driver.cs @@ -49,6 +49,7 @@ using Xamarin.Utils; using Xamarin.Linker; using Registrar; +using ClassRedirector; using ObjCRuntime; namespace Xamarin.Bundler { @@ -794,7 +795,7 @@ static void Compile () if (App.Registrar == RegistrarMode.Static) { registrarPath = Path.Combine (App.Cache.Location, "registrar.m"); var registrarH = Path.Combine (App.Cache.Location, "registrar.h"); - BuildTarget.StaticRegistrar.Generate (BuildTarget.Resolver.ResolverCache.Values, registrarH, registrarPath, out initialization_method, App.ClassMapPath); + BuildTarget.StaticRegistrar.Generate (BuildTarget.Resolver.ResolverCache.Values, registrarH, registrarPath, out initialization_method); var platform_assembly = BuildTarget.Resolver.ResolverCache.First ((v) => v.Value.Name.Name == BuildTarget.StaticRegistrar.PlatformAssembly).Value; Frameworks.Gather (App, platform_assembly, BuildTarget.Frameworks, BuildTarget.WeakFrameworks); diff --git a/tools/mmp/mmp.csproj b/tools/mmp/mmp.csproj index c72abec43d49..e3725497a7d2 100644 --- a/tools/mmp/mmp.csproj +++ b/tools/mmp/mmp.csproj @@ -364,6 +364,9 @@ tools\common\CSToObjCMap.cs + + tools\common\Rewriter.cs + tools\common\ObjCNameIndex.cs diff --git a/tools/mtouch/BuildTasks.mtouch.cs b/tools/mtouch/BuildTasks.mtouch.cs index fed490c8e3d4..e97524445c06 100644 --- a/tools/mtouch/BuildTasks.mtouch.cs +++ b/tools/mtouch/BuildTasks.mtouch.cs @@ -7,6 +7,7 @@ using Xamarin.MacDev; using Xamarin.Utils; +using ClassRedirector; namespace Xamarin.Bundler { public abstract class ProcessTask : BuildTask { @@ -120,7 +121,8 @@ public override IEnumerable Outputs { protected override void Execute () { - Target.StaticRegistrar.Generate (Target.Assemblies.Select ((a) => a.AssemblyDefinition), RegistrarHeaderPath, RegistrarCodePath, out var initialization_name, Target.App.ClassMapPath); + var assemblies = Target.Assemblies.Select ((a) => a.AssemblyDefinition); + Target.StaticRegistrar.Generate (assemblies, RegistrarHeaderPath, RegistrarCodePath, out var initialization_name); RegistrationMethods.Add (initialization_name); } } diff --git a/tools/mtouch/mtouch.csproj b/tools/mtouch/mtouch.csproj index 7c99af4f9ac9..64051977bb12 100644 --- a/tools/mtouch/mtouch.csproj +++ b/tools/mtouch/mtouch.csproj @@ -338,6 +338,9 @@ tools\common\OSPlatformAttributeExtensions.cs + + tools\common\Rewriter.cs + tools\common\CSToObjCMap.cs