From a76a137f55fe32421ade7b3184b0210cbef717a4 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 12 Sep 2025 14:58:09 +1200 Subject: [PATCH 1/8] Add support for v3 of the AssemblyStore format Resolves #4491 - https://github.com/getsentry/sentry-dotnet/issues/4491 --- .../AndroidAssemblyReaderFactory.cs | 18 +- .../V1/ATTRIBUTION.txt | 31 -- .../V1/AndroidAssemblyDirectoryReaderV1.cs | 53 -- .../V1/AndroidAssemblyStoreReaderV1.cs | 510 ------------------ .../V2/AssemblyStoreItem.cs | 6 +- .../V2/StoreReaderV2.Classes.cs | 15 +- .../V2/StoreReaderV2.cs | 18 +- .../AndroidAssemblyReaderTests.cs | 20 +- ...iApprovalTests.Run.DotNet10_0.verified.txt | 20 + ...Sentry.Android.AssemblyReader.Tests.csproj | 20 +- 10 files changed, 73 insertions(+), 638 deletions(-) delete mode 100644 src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt delete mode 100644 src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs delete mode 100644 src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs create mode 100644 test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt diff --git a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs index e111a46f71..128e0cbc30 100644 --- a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs +++ b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader; @@ -19,28 +18,13 @@ public static IAndroidAssemblyReader Open(string apkPath, IList supporte { logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath); -#if NET9_0 - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V2 APK layout."); if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V2"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore"); return readerV2; } logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger); -#else - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V1 APK layout."); - - var zipArchive = ZipFile.OpenRead(apkPath); - if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null) - { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V1"); - return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger); - } - - logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger); -#endif } } diff --git a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt b/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt deleted file mode 100644 index 16c3f15ed7..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt +++ /dev/null @@ -1,31 +0,0 @@ -Parts of the code in this subdirectory have been adapted from -https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - -The original license is as follows: - - -Xamarin.Android SDK - -The MIT License (MIT) - -Copyright (c) .NET Foundation Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs deleted file mode 100644 index 7b5e1c1a7e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) { } - - public PEReader? TryReadAssembly(string name) - { - if (File.Exists(name)) - { - // The assembly is already extracted to the file system. Just read it. - var stream = File.OpenRead(name); - return new PEReader(stream); - } - - var zipEntry = FindAssembly(name); - if (zipEntry is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName); - - // We need a seekable stream for the PEReader (or even to check whether the DLL is compressed), so make a copy. - var memStream = zipEntry.Extract(); - return ArchiveUtils.CreatePEReader(name, memStream, Logger); - } - - private ZipArchiveEntry? FindAssembly(string name) - { - var zipEntry = ZipArchive.GetEntry($"assemblies/{name}"); - - if (zipEntry is null) - { - foreach (var abi in SupportedAbis) - { - if (abi.Length > 0) - { - zipEntry = ZipArchive.GetEntry($"assemblies/{abi}/{name}"); - if (zipEntry is not null) - { - break; - } - } - } - } - - return zipEntry; - } -} diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs deleted file mode 100644 index 60c947b23e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs +++ /dev/null @@ -1,510 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// See https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores -internal sealed class AndroidAssemblyStoreReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - private readonly AssemblyStoreExplorer _explorer; - - public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) - { - _explorer = new(zip, supportedAbis, logger); - } - - public PEReader? TryReadAssembly(string name) - { - var assembly = TryFindAssembly(name); - if (assembly is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch); - - var stream = assembly.GetImage(); - if (stream is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name); - return null; - } - - return ArchiveUtils.CreatePEReader(name, stream, Logger); - } - - private AssemblyStoreAssembly? TryFindAssembly(string name) - { - if (_explorer.AssembliesByName.TryGetValue(name, out var assembly)) - { - return assembly; - } - - if (name.EndsWith(".dll", ignoreCase: true, CultureInfo.InvariantCulture) || - name.EndsWith(".exe", ignoreCase: true, CultureInfo.InvariantCulture)) - { - if (_explorer.AssembliesByName.TryGetValue(name.Substring(0, name.Length - 4), out assembly)) - { - return assembly; - } - } - - return null; - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreExplorer - { - private AssemblyStoreReader? _indexStore; - private readonly AssemblyStoreManifestReader _manifest; - private readonly DebugLogger? _logger; - - public IDictionary AssembliesByName { get; } = - new SortedDictionary(StringComparer.OrdinalIgnoreCase); - - public IDictionary AssembliesByHash32 { get; } = - new Dictionary(); - - public IDictionary AssembliesByHash64 { get; } = - new Dictionary(); - - public List Assemblies { get; } = new(); - - public IDictionary> Stores { get; } = - new SortedDictionary>(); - - public AssemblyStoreExplorer(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - { - _logger = logger; - _manifest = new AssemblyStoreManifestReader(zip.GetEntry("assemblies/assemblies.manifest")!.Open()); - - TryAddStore(zip, null); - foreach (var abi in supportedAbis) - { - if (!string.IsNullOrEmpty(abi)) - { - TryAddStore(zip, abi); - } - } - - zip.Dispose(); - ProcessStores(); - } - - private void ProcessStores() - { - if (Stores.Count == 0 || _indexStore == null) - { - return; - } - - ProcessIndex(_indexStore.GlobalIndex32, "32", (he, assembly) => - { - assembly.Hash32 = (uint)he.Hash; - assembly.RuntimeIndex = he.MappingIndex; - - if (_manifest.EntriesByHash32.TryGetValue(assembly.Hash32, out var me)) - { - assembly.Name = me.Name; - } - - if (!AssembliesByHash32.ContainsKey(assembly.Hash32)) - { - AssembliesByHash32.Add(assembly.Hash32, assembly); - } - }); - - ProcessIndex(_indexStore.GlobalIndex64, "64", (he, assembly) => - { - assembly.Hash64 = he.Hash; - if (assembly.RuntimeIndex != he.MappingIndex) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"assembly with hashes 0x{assembly.Hash32} and 0x{assembly.Hash64} has a different 32-bit runtime index ({assembly.RuntimeIndex}) than the 64-bit runtime index({he.MappingIndex})"); - } - - if (_manifest.EntriesByHash64.TryGetValue(assembly.Hash64, out var me)) - { - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} did not match any assembly name in the manifest"); - assembly.Name = me.Name; - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"64-bit hash 0x{assembly.Hash64:x} did not match any assembly name in the manifest"); - } - } - else if (!string.Equals(assembly.Name, me.Name, StringComparison.Ordinal)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} maps to assembly name '{assembly.Name}', however 64-bit hash 0x{assembly.Hash64:x} for the same entry matches assembly name '{me.Name}'"); - } - } - - if (!AssembliesByHash64.ContainsKey(assembly.Hash64)) - { - AssembliesByHash64.Add(assembly.Hash64, assembly); - } - }); - - foreach (var kvp in Stores) - { - var list = kvp.Value; - if (list.Count < 2) - { - continue; - } - - var template = list[0]; - for (var i = 1; i < list.Count; i++) - { - var other = list[i]; - if (!template.HasIdenticalContent(other)) - { - throw new Exception( - $"Store ID {template.StoreId} for architecture {other.Arch} is not identical to other stores with the same ID"); - } - } - } - - void ProcessIndex(List index, string bitness, - Action assemblyHandler) - { - foreach (var he in index) - { - if (!Stores.TryGetValue(he.StoreId, out var storeList)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, $"store with id {he.StoreId} not part of the set"); - continue; - } - - foreach (var store in storeList) - { - if (he.LocalStoreIndex >= (uint)store.Assemblies.Count) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"{bitness}-bit index entry with hash 0x{he.Hash:x} has invalid store {store.StoreId} index {he.LocalStoreIndex} (maximum allowed is {store.Assemblies.Count})"); - continue; - } - - var assembly = store.Assemblies[(int)he.LocalStoreIndex]; - assemblyHandler(he, assembly); - - if (!AssembliesByName.ContainsKey(assembly.Name)) - { - AssembliesByName.Add(assembly.Name, assembly); - } - } - } - } - } - - private void TryAddStore(ZipArchive archive, string? abi) - { - var infix = abi is null ? "" : $".{abi}"; - if (archive.GetEntry($"assemblies/assemblies{infix}.blob") is { } zipEntry) - { - var memStream = new MemoryStream((int)zipEntry.Length); - using (var zipStream = zipEntry.Open()) - { - zipStream.CopyTo(memStream); - memStream.Position = 0; - } - - AddStore(new AssemblyStoreReader(memStream, abi)); - } - } - - private void AddStore(AssemblyStoreReader reader) - { - if (reader.HasGlobalIndex) - { - _indexStore = reader; - } - - if (!Stores.TryGetValue(reader.StoreId, out var storeList)) - { - storeList = new List(); - Stores.Add(reader.StoreId, storeList); - } - - storeList.Add(reader); - - Assemblies.AddRange(reader.Assemblies); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestReader - { - public List Entries { get; } = new(); - public Dictionary EntriesByHash32 { get; } = new(); - public Dictionary EntriesByHash64 { get; } = new(); - - public AssemblyStoreManifestReader(Stream manifest) - { - using var sr = new StreamReader(manifest, Encoding.UTF8, detectEncodingFromByteOrderMarks: false); - ReadManifest(sr); - } - - private void ReadManifest(StreamReader reader) - { - // First line is ignored, it contains headers - reader.ReadLine(); - - // Each subsequent line consists of fields separated with any number of spaces (for the pleasure of a human being reading the manifest) - while (!reader.EndOfStream) - { - var fields = reader.ReadLine()?.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - if (fields == null) - { - continue; - } - - var entry = new AssemblyStoreManifestEntry(fields); - Entries.Add(entry); - if (entry.Hash32 != 0) - { - EntriesByHash32.Add(entry.Hash32, entry); - } - - if (entry.Hash64 != 0) - { - EntriesByHash64.Add(entry.Hash64, entry); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestEntry - { - // Fields are: - // Hash 32 | Hash 64 | Store ID | Store idx | Name - private const int NumberOfFields = 5; - private const int Hash32FieldIndex = 0; - private const int Hash64FieldIndex = 1; - private const int StoreIdFieldIndex = 2; - private const int StoreIndexFieldIndex = 3; - private const int NameFieldIndex = 4; - - public uint Hash32 { get; } - public ulong Hash64 { get; } - public uint StoreId { get; } - public uint IndexInStore { get; } - public string Name { get; } - - public AssemblyStoreManifestEntry(string[] fields) - { - if (fields.Length != NumberOfFields) - { - throw new ArgumentOutOfRangeException(nameof(fields), "Invalid number of fields"); - } - - Hash32 = GetUInt32(fields[Hash32FieldIndex]); - Hash64 = GetUInt64(fields[Hash64FieldIndex]); - StoreId = GetUInt32(fields[StoreIdFieldIndex]); - IndexInStore = GetUInt32(fields[StoreIndexFieldIndex]); - Name = fields[NameFieldIndex].Trim(); - } - - private static uint GetUInt32(string value) - { - if (uint.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static ulong GetUInt64(string value) - { - if (ulong.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static string PrepHexValue(string value) - { - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - return value.Substring(2); - } - - return value; - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreHashEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreHashEntry - { - public bool Is32Bit { get; } - public ulong Hash { get; } - public uint MappingIndex { get; } - public uint LocalStoreIndex { get; } - public uint StoreId { get; } - - internal AssemblyStoreHashEntry(BinaryReader reader, bool is32Bit) - { - Is32Bit = is32Bit; - Hash = reader.ReadUInt64(); - MappingIndex = reader.ReadUInt32(); - LocalStoreIndex = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreReader - { - // These two constants must be identical to the native ones in src/monodroid/jni/xamarin-app.hh - private const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian - private const uint ASSEMBLY_STORE_FORMAT_VERSION = 1; // The highest format version this reader understands - - private readonly MemoryStream _storeData; - - public uint Version { get; private set; } - public uint LocalEntryCount { get; private set; } - public uint GlobalEntryCount { get; private set; } - public uint StoreId { get; private set; } - public List Assemblies { get; } - public List GlobalIndex32 { get; } = new(); - public List GlobalIndex64 { get; } = new(); - public string Arch { get; } - - public bool HasGlobalIndex => StoreId == 0; - - public AssemblyStoreReader(MemoryStream store, string? arch = null) - { - Arch = arch ?? string.Empty; - _storeData = store; - using var reader = new BinaryReader(store, Encoding.UTF8, leaveOpen: true); - ReadHeader(reader); - - Assemblies = new List(); - ReadLocalEntries(reader, Assemblies); - if (HasGlobalIndex) - { - ReadGlobalIndex(reader, GlobalIndex32, GlobalIndex64); - } - } - - internal MemoryStream? GetAssemblyImageSlice(AssemblyStoreAssembly assembly) => - GetDataSlice(assembly.DataOffset, assembly.DataSize); - - internal MemoryStream? GetAssemblyDebugDataSlice(AssemblyStoreAssembly assembly) => - assembly.DebugDataOffset == 0 ? null : GetDataSlice(assembly.DebugDataOffset, assembly.DebugDataSize); - - internal MemoryStream? GetAssemblyConfigSlice(AssemblyStoreAssembly assembly) => - assembly.ConfigDataOffset == 0 ? null : GetDataSlice(assembly.ConfigDataOffset, assembly.ConfigDataSize); - - private MemoryStream? GetDataSlice(uint offset, uint size) => - size == 0 ? null : new ArchiveUtils.MemorySlice(_storeData, (int)offset, (int)size); - - public bool HasIdenticalContent(AssemblyStoreReader other) - { - return - other.Version == Version && - other.LocalEntryCount == LocalEntryCount && - other.GlobalEntryCount == GlobalEntryCount && - other.StoreId == StoreId && - other.Assemblies.Count == Assemblies.Count && - other.GlobalIndex32.Count == GlobalIndex32.Count && - other.GlobalIndex64.Count == GlobalIndex64.Count; - } - - private void ReadHeader(BinaryReader reader) - { - if (reader.ReadUInt32() != ASSEMBLY_STORE_MAGIC) - { - throw new InvalidOperationException("Invalid header magic number"); - } - - Version = reader.ReadUInt32(); - if (Version == 0) - { - throw new InvalidOperationException("Invalid version number: 0"); - } - - if (Version > ASSEMBLY_STORE_FORMAT_VERSION) - { - throw new InvalidOperationException( - $"Store format version {Version} is higher than the one understood by this reader, {ASSEMBLY_STORE_FORMAT_VERSION}"); - } - - LocalEntryCount = reader.ReadUInt32(); - GlobalEntryCount = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - - private void ReadLocalEntries(BinaryReader reader, List assemblies) - { - for (uint i = 0; i < LocalEntryCount; i++) - { - assemblies.Add(new AssemblyStoreAssembly(reader, this)); - } - } - - private void ReadGlobalIndex(BinaryReader reader, List index32, - List index64) - { - ReadIndex(true, index32); - ReadIndex(false, index64); - - void ReadIndex(bool is32Bit, List index) - { - for (uint i = 0; i < GlobalEntryCount; i++) - { - index.Add(new AssemblyStoreHashEntry(reader, is32Bit)); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreAssembly.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreAssembly - { - public uint DataOffset { get; } - public uint DataSize { get; } - public uint DebugDataOffset { get; } - public uint DebugDataSize { get; } - public uint ConfigDataOffset { get; } - public uint ConfigDataSize { get; } - - public uint Hash32 { get; set; } - public ulong Hash64 { get; set; } - public string Name { get; set; } = string.Empty; - public uint RuntimeIndex { get; set; } - - public AssemblyStoreReader Store { get; } - - internal AssemblyStoreAssembly(BinaryReader reader, AssemblyStoreReader store) - { - Store = store; - - DataOffset = reader.ReadUInt32(); - DataSize = reader.ReadUInt32(); - DebugDataOffset = reader.ReadUInt32(); - DebugDataSize = reader.ReadUInt32(); - ConfigDataOffset = reader.ReadUInt32(); - ConfigDataSize = reader.ReadUInt32(); - } - - public MemoryStream? GetImage() => Store.GetAssemblyImageSlice(this); - - public MemoryStream? GetDebugData() => Store.GetAssemblyDebugDataSlice(this); - - public MemoryStream? GetAssemblyConfig() => Store.GetAssemblyConfigSlice(this); - } -} diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs index a132f51ead..2ecde5fd48 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs @@ -1,5 +1,7 @@ /* * Adapted from https://github.com/dotnet/android/blob/86260ed36dfe1a90c8ed6a2bb1cd0607d637f403/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ @@ -17,11 +19,13 @@ internal abstract class AssemblyStoreItem public uint ConfigOffset { get; protected set; } public uint ConfigSize { get; protected set; } public AndroidTargetArch TargetArch { get; protected set; } + public bool Ignore { get; } - protected AssemblyStoreItem(string name, bool is64Bit, List hashes) + protected AssemblyStoreItem(string name, bool is64Bit, List hashes, bool ignore) { Name = name; Hashes = hashes.AsReadOnly(); Is64Bit = is64Bit; + Ignore = ignore; } } diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs index 85aa91ba89..e828ddfdef 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs @@ -1,5 +1,7 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ @@ -33,11 +35,13 @@ private sealed class IndexEntry { public readonly ulong name_hash; public readonly uint descriptor_index; + public readonly bool ignore; - public IndexEntry(ulong name_hash, uint descriptor_index) + public IndexEntry(ulong name_hash, uint descriptor_index, bool ignore) { this.name_hash = name_hash; this.descriptor_index = descriptor_index; + this.ignore = ignore; } } @@ -57,8 +61,9 @@ private sealed class EntryDescriptor private sealed class StoreItemV2 : AssemblyStoreItem { - public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, EntryDescriptor descriptor) - : base(name, is64Bit, IndexToHashes(indexEntries)) + public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, + EntryDescriptor descriptor, bool ignore) + : base(name, is64Bit, IndexToHashes(indexEntries), ignore) { DataOffset = descriptor.data_offset; DataSize = descriptor.data_size; @@ -86,11 +91,13 @@ private sealed class TemporaryItem public readonly string Name; public readonly List IndexEntries = new List(); public readonly EntryDescriptor Descriptor; + public readonly bool Ignored; - public TemporaryItem(string name, EntryDescriptor descriptor) + public TemporaryItem(string name, EntryDescriptor descriptor, bool ignored) { Name = name; Descriptor = descriptor; + Ignored = ignored; } } } diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs index 97bac95694..10cb4740bb 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs @@ -1,5 +1,7 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ @@ -8,8 +10,13 @@ namespace Sentry.Android.AssemblyReader.V2; internal partial class StoreReaderV2 : AssemblyStoreReader { // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones +#if NET9_0 private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; +#else + const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; +#endif private const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; private const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; private const uint ASSEMBLY_STORE_ABI_ARM = 0x00020000; @@ -178,7 +185,12 @@ protected override void Prepare() } uint descriptor_index = reader.ReadUInt32(); - index.Add(new IndexEntry(name_hash, descriptor_index)); +#if NET10_0_OR_GREATER + bool ignore = reader.ReadByte () != 0; +#else + bool ignore = false; +#endif + index.Add(new IndexEntry(name_hash, descriptor_index, ignore)); } var descriptors = new List(); @@ -218,7 +230,7 @@ protected override void Prepare() { if (!tempItems.TryGetValue(ie.descriptor_index, out TemporaryItem? item)) { - item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index]); + item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore); tempItems.Add(ie.descriptor_index, item); } item.IndexEntries.Add(ie); @@ -233,7 +245,7 @@ protected override void Prepare() foreach (var kvp in tempItems) { TemporaryItem ti = kvp.Value; - var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor); + var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored); storeItems.Add(item); } Assemblies = storeItems.AsReadOnly(); diff --git a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs index f0d1a50c64..7f52d773fe 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs +++ b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader.Tests; @@ -6,13 +5,10 @@ namespace Sentry.Android.AssemblyReader.Tests; public class AndroidAssemblyReaderTests { private readonly ITestOutputHelper _output; - #if NET10_0 private static string TargetFramework => "net10.0"; #elif NET9_0 private static string TargetFramework => "net9.0"; -#elif NET8_0 - private static string TargetFramework => "net8.0"; #else // Adding a new TFM to the project? Include it above #error "Target Framework not yet supported for AndroidAssemblyReader" @@ -54,11 +50,11 @@ public void CreatesCorrectStoreReader() using var sut = GetSut(isAot: false, isAssemblyStore: true, isCompressed: true); switch (TargetFramework) { - case "net9.0": + case "net10.0": Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -74,11 +70,11 @@ public void CreatesCorrectArchiveReader() using var sut = GetSut(isAot: false, isAssemblyStore: false, isCompressed: true); switch (TargetFramework) { - case "net9.0": + case "net10.0": Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -95,11 +91,7 @@ public void ReturnsNullIfAssemblyDoesntExist(bool isAssemblyStore) } public static IEnumerable ReadsAssemblyPermutations => -#if NET8_0 - from isAot in new[] { false } -#else from isAot in new[] { true, false } -#endif from isStore in new[] { true, false } from isCompressed in new[] { true, false } from assemblyName in new[] { "Mono.Android.dll", "System.Private.CoreLib.dll" } diff --git a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..67bbca78b3 --- /dev/null +++ b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,20 @@ +namespace Sentry.Android.AssemblyReader +{ + public static class AndroidAssemblyReaderFactory + { + public static Sentry.Android.AssemblyReader.IAndroidAssemblyReader Open(string apkPath, System.Collections.Generic.IList supportedAbis, Sentry.Android.AssemblyReader.DebugLogger? logger = null) { } + } + public delegate void DebugLogger(Sentry.Android.AssemblyReader.DebugLoggerLevel level, string message, params object?[] args); + public enum DebugLoggerLevel : short + { + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal = 4, + } + public interface IAndroidAssemblyReader : System.IDisposable + { + System.Reflection.PortableExecutable.PEReader? TryReadAssembly(string name); + } +} \ No newline at end of file diff --git a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj index 8d50df6bec..f76f8eb0d5 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj +++ b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj @@ -1,7 +1,7 @@ - net9.0 + $(LatestTfm);$(PreviousTfm) $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) enable @@ -42,11 +42,21 @@ <_TestAPK Include="2" Properties="_Aot=False;_Store=False;_Compressed=True" /> <_TestAPK Include="3" Properties="_Aot=False;_Store=True;_Compressed=False" /> <_TestAPK Include="4" Properties="_Aot=False;_Store=True;_Compressed=True" /> + + + + + From 8e28d70c4c01417e651db21f917c7f6ee7b5a721 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 12 Sep 2025 03:23:30 +0000 Subject: [PATCH 2/8] Format code --- src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs index 10cb4740bb..cf632d9e1d 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs @@ -14,8 +14,8 @@ internal partial class StoreReaderV2 : AssemblyStoreReader private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; #else - const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant - const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; + private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; #endif private const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; private const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; From 05e17a2cbdc78dc01ee8e725ce3ddae589ae3dfb Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 18 Sep 2025 16:21:41 +1200 Subject: [PATCH 3/8] Fixed AndroidAssemblyReader tests --- test/AndroidTestApp/AndroidTestApp.csproj | 2 ++ ...Sentry.Android.AssemblyReader.Tests.csproj | 24 ++++++------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/test/AndroidTestApp/AndroidTestApp.csproj b/test/AndroidTestApp/AndroidTestApp.csproj index 261fc05b24..cefce5290f 100644 --- a/test/AndroidTestApp/AndroidTestApp.csproj +++ b/test/AndroidTestApp/AndroidTestApp.csproj @@ -2,6 +2,7 @@ net10.0-android;net9.0-android false + false 21 Exe enable @@ -9,5 +10,6 @@ com.companyname.AndroidTestApp 1 1.0 + android-x64 diff --git a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj index f76f8eb0d5..a892724b49 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj +++ b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj @@ -20,7 +20,7 @@ - + @@ -42,21 +42,10 @@ <_TestAPK Include="2" Properties="_Aot=False;_Store=False;_Compressed=True" /> <_TestAPK Include="3" Properties="_Aot=False;_Store=True;_Compressed=False" /> <_TestAPK Include="4" Properties="_Aot=False;_Store=True;_Compressed=True" /> - - - - - + <_TestAPK Include="5" Properties="_Aot=True;_Store=False;_Compressed=False" /> + <_TestAPK Include="6" Properties="_Aot=True;_Store=False;_Compressed=True" /> + <_TestAPK Include="7" Properties="_Aot=True;_Store=True;_Compressed=False" /> + <_TestAPK Include="8" Properties="_Aot=True;_Store=True;_Compressed=True" /> @@ -67,7 +56,8 @@ ..\AndroidTestApp\bin\$(TargetFramework)\$(_ConfigString)\com.companyname.AndroidTestApp-Signed.apk TestAPKs\$(TargetFramework)-$(_ConfigString).apk - + + From 9f962172a97e4499f41a861fce66510260beaa35 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 18 Sep 2025 16:38:15 +1200 Subject: [PATCH 4/8] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 043f8b003b..06fb9a4b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### BREAKING CHANGES - This release adds support for .NET 10 and drops support for net8.0-android, net8.0-ios, net8.0-maccatalyst and net8.0-windows10.0.19041.0 ([#4461](https://github.com/getsentry/sentry-dotnet/pull/4461)) +- Added support for v3 of the Android AssemblyStore format that is used in .NET 10 and dropped support for v1 that was used in .NET 8 ([#4516](https://github.com/getsentry/sentry-dotnet/pull/4516)) ## Unreleased From d3aa5bc57a3cf62777cdddd41e6daaca657691ee Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 25 Sep 2025 10:53:13 +1200 Subject: [PATCH 5/8] Renamed v2 classes and remove remaining vestiges of v1 --- .../AndroidAssemblyReaderFactory.cs | 4 +- ...2.cs => AndroidAssemblyDirectoryReader.cs} | 4 +- ...derV2.cs => AndroidAssemblyStoreReader.cs} | 8 ++-- .../V2/AssemblyStoreExplorer.cs | 21 +++------- .../V2/AssemblyStoreReader.cs | 18 +-------- ...erV2.Classes.cs => StoreReader.Classes.cs} | 2 +- .../V2/{StoreReaderV2.cs => StoreReader.cs} | 6 +-- .../V2/StoreReaderV1.cs | 38 ------------------- .../AndroidAssemblyReaderTests.cs | 8 ++-- 9 files changed, 23 insertions(+), 86 deletions(-) rename src/Sentry.Android.AssemblyReader/V2/{AndroidAssemblyDirectoryReaderV2.cs => AndroidAssemblyDirectoryReader.cs} (98%) rename src/Sentry.Android.AssemblyReader/V2/{AndroidAssemblyStoreReaderV2.cs => AndroidAssemblyStoreReader.cs} (94%) rename src/Sentry.Android.AssemblyReader/V2/{StoreReaderV2.Classes.cs => StoreReader.Classes.cs} (98%) rename src/Sentry.Android.AssemblyReader/V2/{StoreReaderV2.cs => StoreReader.cs} (98%) delete mode 100644 src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs diff --git a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs index 128e0cbc30..2e4b8d03ab 100644 --- a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs +++ b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs @@ -18,13 +18,13 @@ public static IAndroidAssemblyReader Open(string apkPath, IList supporte { logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath); - if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) + if (AndroidAssemblyStoreReader.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) { logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore"); return readerV2; } logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger); + return new AndroidAssemblyDirectoryReader(apkPath, supportedAbis, logger); } } diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs similarity index 98% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs index b250640d1f..ca2b96d886 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs @@ -1,13 +1,13 @@ namespace Sentry.Android.AssemblyReader.V2; // The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV2 : IAndroidAssemblyReader +internal sealed class AndroidAssemblyDirectoryReader : IAndroidAssemblyReader { private DebugLogger? Logger { get; } private HashSet SupportedArchitectures { get; } = new(); private readonly ArchiveAssemblyHelper _archiveAssemblyHelper; - public AndroidAssemblyDirectoryReaderV2(string apkPath, IList supportedAbis, DebugLogger? logger) + public AndroidAssemblyDirectoryReader(string apkPath, IList supportedAbis, DebugLogger? logger) { Logger = logger; foreach (var abi in supportedAbis) diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs similarity index 94% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs index 2840c7ec67..0e9c6586f4 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs @@ -1,17 +1,17 @@ namespace Sentry.Android.AssemblyReader.V2; -internal class AndroidAssemblyStoreReaderV2 : IAndroidAssemblyReader +internal class AndroidAssemblyStoreReader : IAndroidAssemblyReader { private readonly IList _explorers; private readonly DebugLogger? _logger; - private AndroidAssemblyStoreReaderV2(IList explorers, DebugLogger? logger) + private AndroidAssemblyStoreReader(IList explorers, DebugLogger? logger) { _explorers = explorers; _logger = logger; } - public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReaderV2? reader) + public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReader? reader) { List supportedExplorers = []; @@ -67,7 +67,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D return false; } - reader = new AndroidAssemblyStoreReaderV2(supportedExplorers, logger); + reader = new AndroidAssemblyStoreReader(supportedExplorers, logger); return true; } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs index 0ab9c04e2e..5854d3c070 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs @@ -79,28 +79,19 @@ public static (IList? explorers, string? errorMessage) Op } private static (IList? explorers, string? errorMessage) OpenAab(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabPaths, StoreReader_V1.AabPaths], logger); + => OpenCommon(fi, StoreReader.AabPaths, logger); private static (IList? explorers, string? errorMessage) OpenAabBase(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabBasePaths, StoreReader_V1.AabBasePaths], logger); + => OpenCommon(fi, StoreReader.AabBasePaths, logger); private static (IList? explorers, string? errorMessage) OpenApk(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.ApkPaths, StoreReader_V1.ApkPaths], logger); + => OpenCommon(fi, StoreReader.ApkPaths, logger); - private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, List> pathLists, DebugLogger? logger) + private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, IList paths, DebugLogger? logger) { using var zip = ZipFile.OpenRead(fi.FullName); - - foreach (var paths in pathLists) - { - var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); - if (pathsFound) - { - return (explorers, errorMessage); - } - } - - return (null, "Unable to find any blob entries"); + var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); + return pathsFound ? (explorers, errorMessage) : (null, "Unable to find any blob entries"); } private static (IList? explorers, string? errorMessage, bool pathsFound) TryLoad(FileInfo fi, ZipArchive zip, IList paths, DebugLogger? logger) diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs index 834cb2c36d..a3201754e1 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs @@ -32,23 +32,7 @@ protected AssemblyStoreReader(Stream store, string path, DebugLogger? logger) public static AssemblyStoreReader? Create(Stream store, string path, DebugLogger? logger) { - var reader = MakeReaderReady(new StoreReader_V1(store, path, logger)); - if (reader != null) - { - return reader; - } - - reader = MakeReaderReady(new StoreReaderV2(store, path, logger)); - if (reader != null) - { - return reader; - } - - return null; - } - - private static AssemblyStoreReader? MakeReaderReady(AssemblyStoreReader reader) - { + var reader = new StoreReader(store, path, logger); if (!reader.IsSupported()) { return null; diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs similarity index 98% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs index e828ddfdef..5ed713b6be 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs @@ -7,7 +7,7 @@ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 +internal partial class StoreReader { private sealed class Header { diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs similarity index 98% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.cs index cf632d9e1d..5220bf7dc7 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs @@ -7,7 +7,7 @@ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 : AssemblyStoreReader +internal partial class StoreReader : AssemblyStoreReader { // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones #if NET9_0 @@ -35,7 +35,7 @@ internal partial class StoreReaderV2 : AssemblyStoreReader private Header? header; private ulong elfOffset = 0; - static StoreReaderV2() + static StoreReader() { var paths = new List { GetArchPath (AndroidTargetArch.Arm64), @@ -76,7 +76,7 @@ string GetArchPath(AndroidTargetArch arch, string? root = null) } } - public StoreReaderV2(Stream store, string path, DebugLogger? logger) + public StoreReader(Stream store, string path, DebugLogger? logger) : base(store, path, logger) { supportedVersions = new HashSet { diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs deleted file mode 100644 index c1a3fe3c2d..0000000000 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V1.cs - * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) - */ - -namespace Sentry.Android.AssemblyReader.V2; - -internal class StoreReader_V1 : AssemblyStoreReader -{ - public override string Description => "Assembly store v1"; - public override bool NeedsExtensionInName => false; - - public static IList ApkPaths { get; } - public static IList AabPaths { get; } - public static IList AabBasePaths { get; } - - static StoreReader_V1() - { - ApkPaths = new List().AsReadOnly(); - AabPaths = new List().AsReadOnly(); - AabBasePaths = new List().AsReadOnly(); - } - - public StoreReader_V1(Stream store, string path, DebugLogger? logger) - : base(store, path, logger) - { } - - protected override bool IsSupported() - { - return false; - } - - protected override void Prepare() - { - } - - protected override ulong GetStoreStartDataOffset() => 0; -} diff --git a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs index 7f52d773fe..60b56ffc54 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs +++ b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs @@ -51,10 +51,10 @@ public void CreatesCorrectStoreReader() switch (TargetFramework) { case "net10.0": - Assert.IsType(sut); + Assert.IsType(sut); break; case "net9.0": - Assert.IsType(sut); + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -71,10 +71,10 @@ public void CreatesCorrectArchiveReader() switch (TargetFramework) { case "net10.0": - Assert.IsType(sut); + Assert.IsType(sut); break; case "net9.0": - Assert.IsType(sut); + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); From 29abe0a675ee924f0f50967a17a57cdf11eaf639 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 25 Sep 2025 10:56:59 +1200 Subject: [PATCH 6/8] Revert "Merge branch 'main' into assembly-store-3" This reverts commit 5f9cffa3cf391108f5724f8c43d727a8d86a55db, reversing changes made to d3aa5bc57a3cf62777cdddd41e6daaca657691ee. --- .github/actions/environment/action.yml | 10 +- .github/workflows/build.yml | 10 +- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/device-tests-android.yml | 37 +---- .github/workflows/device-tests-ios.yml | 1 - .github/workflows/release.yml | 2 +- CHANGELOG.md | 33 ----- Directory.Build.props | 6 +- integration-test/cli.Tests.ps1 | 7 - modules/sentry-cocoa.properties | 2 +- modules/sentry-native | 2 +- scripts/device-test.ps1 | 4 +- scripts/generate-cocoa-bindings.ps1 | 3 + src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 13 +- .../PrivateApiDefinitions.cs | 7 + .../Sentry.Bindings.Cocoa.csproj | 2 - .../SwiftApiDefinitions.cs | 132 ------------------ .../SwiftStructsAndEnums.cs | 9 -- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 46 ++---- src/Sentry/Platforms/Native/CFunctions.cs | 23 +-- src/Sentry/Sentry.csproj | 14 +- src/Sentry/buildTransitive/Sentry.targets | 29 +--- test/Directory.Build.props | 2 +- test/Sentry.Tests/SentrySdkTests.cs | 41 ------ 24 files changed, 69 insertions(+), 370 deletions(-) diff --git a/.github/actions/environment/action.yml b/.github/actions/environment/action.yml index 1416f5d689..534eab58dc 100644 --- a/.github/actions/environment/action.yml +++ b/.github/actions/environment/action.yml @@ -67,7 +67,7 @@ runs: distribution: ${{ runner.os == 'Windows' && runner.arch == 'ARM64' && 'microsoft' || 'temurin' }} java-version: '11' - - name: Install Mono (macOS) + - name: Install Mono if: runner.os == 'macOS' shell: bash # Attempt to install Mono, allowing it to fail silently @@ -76,14 +76,6 @@ runs: brew install mono || true brew link --overwrite mono - - name: Install Mono (Ubuntu) - if: ${{ runner.os == 'Linux' && !matrix.container }} - shell: bash - run: | - sudo apt install -y mono-devel - # restore perms for actions/setup-dotnet - sudo chmod -R a+rw /usr/share/dotnet - - name: Install .NET SDK uses: actions/setup-dotnet@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ec3f87361..f278d19a1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 + - os: ubuntu-22.04 # Pin ubuntu to ensure mono is installed rid: linux-x64 - os: ubuntu-22.04-arm rid: linux-arm64 @@ -82,7 +82,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 + - os: ubuntu-22.04 # Pin ubuntu to ensure mono is installed rid: linux-x64 slnf: Sentry-CI-Build-Linux.slnf - os: ubuntu-22.04-arm @@ -269,12 +269,12 @@ jobs: - name: Integration test if: ${{ (matrix.rid != 'linux-musl-x64') && (matrix.rid != 'linux-musl-arm64') }} - uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + uses: getsentry/github-workflows/sentry-cli/integration-test/@v2 with: path: integration-test # For the linux-musl runtimes we have to pin the ContainerBaseImage for preview or RC builds, since - # these don't conform to the normal naming conventions and don't get resolved automatically. We do + # these don't conform to the normal naming conventions and don't get resolved automatically. We do # by passing it as parameter to aot.Tests.ps1 via an environment variable - name: Integration test (musl) if: ${{ (matrix.rid == 'linux-musl-x64') || (matrix.rid == 'linux-musl-arm64') }} @@ -361,7 +361,7 @@ jobs: path: src - name: Test AOT - uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 + uses: getsentry/github-workflows/sentry-cli/integration-test/@v2 env: RuntimeIdentifier: ${{ matrix.rid }} with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 225ac38054..abbea520cf 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: uses: ./.github/actions/environment - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # pin@v2 + uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # pin@v2 with: languages: csharp @@ -49,6 +49,6 @@ jobs: run: dotnet build Sentry-CI-CodeQL.slnf --no-restore --nologo - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # pin@v2 + uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # pin@v2 with: category: '/language:csharp' diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index b234d39df2..62c85e5eeb 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -8,7 +8,6 @@ on: pull_request: paths-ignore: - "**.md" - workflow_dispatch: jobs: build: @@ -65,12 +64,6 @@ jobs: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 - # We don't need the Google APIs, but the default images are not available for 32+ - ANDROID_EMULATOR_TARGET: google_apis - ANDROID_EMULATOR_RAM_SIZE: 2048M - ANDROID_EMULATOR_ARCH: x86_64 - ANDROID_EMULATOR_DISK_SIZE: 4096M - ANDROID_EMULATOR_OPTIONS: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none steps: # See https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ - name: Enable KVM group perms @@ -89,38 +82,22 @@ jobs: path: bin - name: Setup Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # pin@v3 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # pin@v3 # Cached AVD setup per https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md - name: Run Tests - id: first-run - continue-on-error: true timeout-minutes: 40 uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 with: api-level: ${{ matrix.api-level }} - target: ${{ env.ANDROID_EMULATOR_TARGET }} + # We don't need the Google APIs, but the default images are not available for 32+ + target: google_apis force-avd-creation: false - ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} - arch: ${{ env.ANDROID_EMULATOR_ARCH }} - disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} - emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} - disable-animations: false - script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} - - - name: Retry Tests (if previous failed to run) - if: steps.first-run.outcome == 'failure' - timeout-minutes: 40 - uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 - with: - api-level: ${{ matrix.api-level }} - target: ${{ env.ANDROID_EMULATOR_TARGET }} - force-avd-creation: false - ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} - arch: ${{ env.ANDROID_EMULATOR_ARCH }} - disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} - emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} + ram-size: 2048M + arch: x86_64 + disk-size: 4096M + emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 0e118745cc..c56226bdb6 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -8,7 +8,6 @@ on: pull_request: paths-ignore: - "**.md" - workflow_dispatch: jobs: ios-tests: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6cc72a9ad8..1f9b66c130 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cbc46a82..06fb9a4b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,41 +11,8 @@ ### Fixes -- Upload linked PDBs to fix non-IL-stripped symbolication for iOS ([#4527](https://github.com/getsentry/sentry-dotnet/pull/4527)) -- In MAUI Android apps, generate and inject UUID to APK and upload ProGuard mapping to Sentry with the UUID ([#4532](https://github.com/getsentry/sentry-dotnet/pull/4532)) -- Fixed WASM0001 warning when building Blazor WebAssembly projects ([#4519](https://github.com/getsentry/sentry-dotnet/pull/4519)) - -### Dependencies - -- Bump Cocoa SDK from v8.56.0 to v8.56.1 ([#4555](https://github.com/getsentry/sentry-dotnet/pull/4555)) - - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8561) - - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.56.0...8.56.1) -- Bump Native SDK from v0.11.0 to v0.11.1 ([#4557](https://github.com/getsentry/sentry-dotnet/pull/4557)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0111) - - [diff](https://github.com/getsentry/sentry-native/compare/0.11.0...0.11.1) -- Bump CLI from v2.54.0 to v2.55.0 ([#4556](https://github.com/getsentry/sentry-dotnet/pull/4556)) - - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2550) - - [diff](https://github.com/getsentry/sentry-cli/compare/2.54.0...2.55.0) - -## 5.15.1 - -### Fixes - - Fail when building Blazor WASM with Profiling. We don't support profiling in Blazor WebAssembly projects. ([#4512](https://github.com/getsentry/sentry-dotnet/pull/4512)) - Do not overwrite user IP if it is set manually in ASP.NET sdk ([#4513](https://github.com/getsentry/sentry-dotnet/pull/4513)) -- Fix `SentryOptions.Native.SuppressSignalAborts` and `SuppressExcBadAccess` on iOS ([#4521](https://github.com/getsentry/sentry-dotnet/pull/4521)) - -### Dependencies - -- Bump Cocoa SDK from v8.55.1 to v8.56.0 ([#4528](https://github.com/getsentry/sentry-dotnet/pull/4528)) - - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8560) - - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.55.1...8.56.0) -- Bump CLI from v2.53.0 to v2.54.0 ([#4541](https://github.com/getsentry/sentry-dotnet/pull/4541)) - - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2540) - - [diff](https://github.com/getsentry/sentry-cli/compare/2.53.0...2.54.0) -- Bump Native SDK from v0.10.1 to v0.11.0 ([#4542](https://github.com/getsentry/sentry-dotnet/pull/4542)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0110) - - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.0) ## 5.15.0 diff --git a/Directory.Build.props b/Directory.Build.props index c5ca1758a8..61b3d89547 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 6.0.0 - prerelease + 5.15.0 + rc.1 13 true true @@ -102,7 +102,7 @@ - 2.55.0 + 2.53.0 $(MSBuildThisFileDirectory)tools\sentry-cli\$(SentryCLIVersion)\ diff --git a/integration-test/cli.Tests.ps1 b/integration-test/cli.Tests.ps1 index 80612d2178..44a203a5a6 100644 --- a/integration-test/cli.Tests.ps1 +++ b/integration-test/cli.Tests.ps1 @@ -174,13 +174,6 @@ Describe 'MAUI ()' -ForEach @( 'libxamarin-dotnet.dylib', 'maui-app', 'maui-app.pdb', - 'Microsoft.iOS.pdb', - 'Microsoft.Maui.Controls.Compatibility.pdb', - 'Microsoft.Maui.Controls.pdb', - 'Microsoft.Maui.Controls.Xaml.pdb', - 'Microsoft.Maui.Essentials.pdb', - 'Microsoft.Maui.Graphics.pdb', - 'Microsoft.Maui.pdb', 'Sentry' ) $nonZeroNumberRegex = '[1-9][0-9]*'; diff --git a/modules/sentry-cocoa.properties b/modules/sentry-cocoa.properties index 8d3c842313..cb992f6d1a 100644 --- a/modules/sentry-cocoa.properties +++ b/modules/sentry-cocoa.properties @@ -1,2 +1,2 @@ -version = 8.56.1 +version = 8.55.1 repo = https://github.com/getsentry/sentry-cocoa diff --git a/modules/sentry-native b/modules/sentry-native index 075b3bfee1..11f94efc64 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 075b3bfee1dbb85fa10d50df631286196943a3e0 +Subproject commit 11f94efc64d55e90aef9456ce01716c846ae1732 diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index f9d5c30868..7822b81582 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -86,8 +86,8 @@ try { if (!(Get-Command xharness -ErrorAction SilentlyContinue)) { - Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/tmp') - dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25466.1' ` + Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/temp') + dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25412.1' ` --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Pop-Location } diff --git a/scripts/generate-cocoa-bindings.ps1 b/scripts/generate-cocoa-bindings.ps1 index eb1295808e..04d44efeb6 100644 --- a/scripts/generate-cocoa-bindings.ps1 +++ b/scripts/generate-cocoa-bindings.ps1 @@ -278,6 +278,9 @@ $Text = $Text -replace '([\[,] )iOS \(', '$1Introduced (PlatformName.iOS, ' # Make interface partial if we need to access private APIs. Other parts will be defined in PrivateApiDefinitions.cs $Text = $Text -replace '(?m)^interface SentryScope', 'partial $&' +# Prefix SentryBreadcrumb.Serialize and SentryScope.Serialize with new (since these hide the base method) +$Text = $Text -replace '(?m)(^\s*\/\/[^\r\n]*$\s*\[Export \("serialize"\)\]$\s*)(NSDictionary)', '${1}new $2' + $Text = $Text -replace '.*SentryEnvelope .*?[\s\S]*?\n\n', '' $Text = $Text -replace '.*typedef.*SentryOnAppStartMeasurementAvailable.*?[\s\S]*?\n\n', '' diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index f33b1c6815..31997fdfd7 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -225,7 +225,7 @@ interface SentryBreadcrumb : SentrySerializable // -(NSDictionary * _Nonnull)serialize; [Export ("serialize")] - NSDictionary Serialize(); + new NSDictionary Serialize(); // -(BOOL)isEqualToBreadcrumb:(SentryBreadcrumb * _Nonnull)breadcrumb; [Export ("isEqualToBreadcrumb:")] @@ -930,11 +930,6 @@ interface SentrySpan : SentrySerializable [Abstract] [NullAllowed, Export ("baggageHttpHeader")] string BaggageHttpHeader { get; } - - // @required -(NSDictionary * _Nonnull)serialize; - [Abstract] - [Export ("serialize")] - NSDictionary Serialize(); } // @interface SentryHub : NSObject @@ -1499,10 +1494,6 @@ interface SentryOptions [Export ("attachScreenshot")] bool AttachScreenshot { get; set; } - // @property (nonatomic, strong) SentryViewScreenshotOptions * _Nonnull screenshot; - [Export ("screenshot", ArgumentSemantic.Strong)] - SentryViewScreenshotOptions Screenshot { get; set; } - // @property (assign, nonatomic) BOOL attachViewHierarchy; [Export ("attachViewHierarchy")] bool AttachViewHierarchy { get; set; } @@ -1873,7 +1864,7 @@ partial interface SentryScope : SentrySerializable // -(NSDictionary * _Nonnull)serialize; [Export ("serialize")] - NSDictionary Serialize(); + new NSDictionary Serialize(); // -(void)setContextValue:(NSDictionary * _Nonnull)value forKey:(NSString * _Nonnull)key __attribute__((swift_name("setContext(value:key:)"))); [Export ("setContextValue:forKey:")] diff --git a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs index 55f59db3fe..ccd5d1865d 100644 --- a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs @@ -17,6 +17,13 @@ partial interface SentryScope // The following types are type-forwarded in various public headers, but have no headers of their own. // Generate stub classes so the APIs that use them can still operate. +[Internal] +[DisableDefaultCtor] +[BaseType (typeof(NSObject))] +interface SentrySession +{ +} + [Internal] [DisableDefaultCtor] [BaseType (typeof(NSObject))] diff --git a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj index 10b3899977..9607318492 100644 --- a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj +++ b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj @@ -12,8 +12,6 @@ $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)../../modules/sentry-cocoa.properties")) $([System.Text.RegularExpressions.Regex]::Match($(SentryCocoaProperties), 'version\s*=\s*([^\s]+)').Groups[1].Value) $(SentryCocoaCache)Sentry-$(SentryCocoaVersion).xcframework - - $(NoWarn);CS0108 diff --git a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs index 1bf7b8ded4..a009e340ec 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs @@ -553,103 +553,6 @@ interface SentrySDK void ClearLogger (); } -// @interface SentrySession : NSObject -[BaseType (typeof(NSObject), Name = "_TtC6Sentry13SentrySession")] -[DisableDefaultCtor] -[Internal] -interface SentrySession -{ - // -(instancetype _Nonnull)initWithReleaseName:(NSString * _Nonnull)releaseName distinctId:(NSString * _Nonnull)distinctId __attribute__((objc_designated_initializer)); - [Export ("initWithReleaseName:distinctId:")] - [DesignatedInitializer] - NativeHandle Constructor (string releaseName, string distinctId); - - // -(instancetype _Nullable)initWithJSONObject:(NSDictionary * _Nonnull)jsonObject __attribute__((objc_designated_initializer)); - [Export ("initWithJSONObject:")] - [DesignatedInitializer] - NativeHandle Constructor (NSDictionary jsonObject); - - // -(void)endSessionExitedWithTimestamp:(NSDate * _Nonnull)timestamp; - [Export ("endSessionExitedWithTimestamp:")] - void EndSessionExitedWithTimestamp (NSDate timestamp); - - // -(void)endSessionCrashedWithTimestamp:(NSDate * _Nonnull)timestamp; - [Export ("endSessionCrashedWithTimestamp:")] - void EndSessionCrashedWithTimestamp (NSDate timestamp); - - // -(void)endSessionAbnormalWithTimestamp:(NSDate * _Nonnull)timestamp; - [Export ("endSessionAbnormalWithTimestamp:")] - void EndSessionAbnormalWithTimestamp (NSDate timestamp); - - // -(void)incrementErrors; - [Export ("incrementErrors")] - void IncrementErrors (); - - // @property (readonly, copy, nonatomic) NSUUID * _Nonnull sessionId; - [Export ("sessionId", ArgumentSemantic.Copy)] - NSUuid SessionId { get; } - - // @property (readonly, copy, nonatomic) NSDate * _Nonnull started; - [Export ("started", ArgumentSemantic.Copy)] - NSDate Started { get; } - - // @property (readonly, nonatomic) enum SentrySessionStatus status; - [Export ("status")] - SentrySessionStatus Status { get; } - - // @property (nonatomic) NSUInteger errors; - [Export ("errors")] - nuint Errors { get; set; } - - // @property (readonly, nonatomic) NSUInteger sequence; - [Export ("sequence")] - nuint Sequence { get; } - - // @property (readonly, copy, nonatomic) NSString * _Nonnull distinctId; - [Export ("distinctId")] - string DistinctId { get; } - - // @property (readonly, nonatomic, strong) NSNumber * _Nullable flagInit; - [NullAllowed, Export ("flagInit", ArgumentSemantic.Strong)] - NSNumber FlagInit { get; } - - // @property (readonly, copy, nonatomic) NSDate * _Nullable timestamp; - [NullAllowed, Export ("timestamp", ArgumentSemantic.Copy)] - NSDate Timestamp { get; } - - // @property (readonly, nonatomic, strong) NSNumber * _Nullable duration; - [NullAllowed, Export ("duration", ArgumentSemantic.Strong)] - NSNumber Duration { get; } - - // @property (readonly, copy, nonatomic) NSString * _Nullable releaseName; - [NullAllowed, Export ("releaseName")] - string ReleaseName { get; } - - // @property (copy, nonatomic) NSString * _Nullable environment; - [NullAllowed, Export ("environment")] - string Environment { get; set; } - - // @property (nonatomic, strong) SentryUser * _Nullable user; - [NullAllowed, Export ("user", ArgumentSemantic.Strong)] - SentryUser User { get; set; } - - // @property (copy, nonatomic) NSString * _Nullable abnormalMechanism; - [NullAllowed, Export ("abnormalMechanism")] - string AbnormalMechanism { get; set; } - - // -(NSDictionary * _Nonnull)serialize __attribute__((warn_unused_result(""))); - [Export ("serialize")] - NSDictionary Serialize(); - - // -(void)setFlagInit; - [Export ("setFlagInit")] - void SetFlagInit (); - - // -(id _Nonnull)copyWithZone:(struct _NSZone * _Nullable)zone __attribute__((warn_unused_result(""))); - // [Export ("copyWithZone:")] - // unsafe NSObject CopyWithZone ([NullAllowed] _NSZone* zone); -} - // @interface SentryUserFeedback : NSObject [BaseType(typeof(NSObject))] [DisableDefaultCtor] @@ -844,41 +747,6 @@ interface SentryUserFeedbackWidgetConfiguration // IntPtr Constructor(); // } -// @interface SentryViewScreenshotOptions : NSObject -[BaseType (typeof(NSObject), Name = "_TtC6Sentry27SentryViewScreenshotOptions")] -[Internal] -interface SentryViewScreenshotOptions //: ISentryRedactOptions -{ - // @property (nonatomic) BOOL enableViewRendererV2; - [Export ("enableViewRendererV2")] - bool EnableViewRendererV2 { get; set; } - - // @property (nonatomic) BOOL enableFastViewRendering; - [Export ("enableFastViewRendering")] - bool EnableFastViewRendering { get; set; } - - // @property (nonatomic) BOOL maskAllImages; - [Export ("maskAllImages")] - bool MaskAllImages { get; set; } - - // @property (nonatomic) BOOL maskAllText; - [Export ("maskAllText")] - bool MaskAllText { get; set; } - - // @property (copy, nonatomic) NSArray * _Nonnull maskedViewClasses; - [Export ("maskedViewClasses", ArgumentSemantic.Copy)] - Class[] MaskedViewClasses { get; set; } - - // @property (copy, nonatomic) NSArray * _Nonnull unmaskedViewClasses; - [Export ("unmaskedViewClasses", ArgumentSemantic.Copy)] - Class[] UnmaskedViewClasses { get; set; } - - // -(instancetype _Nonnull)initWithEnableViewRendererV2:(BOOL)enableViewRendererV2 enableFastViewRendering:(BOOL)enableFastViewRendering maskAllText:(BOOL)maskAllText maskAllImages:(BOOL)maskAllImages maskedViewClasses:(NSArray * _Nonnull)maskedViewClasses unmaskedViewClasses:(NSArray * _Nonnull)unmaskedViewClasses __attribute__((objc_designated_initializer)); - [Export ("initWithEnableViewRendererV2:enableFastViewRendering:maskAllText:maskAllImages:maskedViewClasses:unmaskedViewClasses:")] - [DesignatedInitializer] - NativeHandle Constructor (bool enableViewRendererV2, bool enableFastViewRendering, bool maskAllText, bool maskAllImages, Class[] maskedViewClasses, Class[] unmaskedViewClasses); -} - // @protocol SentryViewScreenshotProvider [Protocol (Name = "_TtP6Sentry28SentryViewScreenshotProvider_")] [Model] diff --git a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs index 1453151744..806daffd87 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs @@ -69,15 +69,6 @@ internal enum SentryRRWebEventType : long Custom = 5 } -[Native] -internal enum SentrySessionStatus : ulong -{ - Ok = 0, - Exited = 1, - Crashed = 2, - Abnormal = 3 -} - [Native] internal enum SentryTransactionNameSource : long { diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 32dd3e40e0..9cd167dc0e 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -195,15 +195,20 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( => ProcessOnBeforeSend(options, evt, CurrentHub); /// - /// Apply suppression logic for redundant native `SIGABRT` and `EXC_BAD_ACCESS` crash events - /// that have already been captured as managed exceptions by the Sentry.NET SDK to avoid sending - /// duplicate events to Sentry - once managed and once native. - /// - /// The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK - /// But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. + /// This overload allows us to inject an IHub for testing. During normal execution, the CurrentHub is used. + /// However, since this class is static, there's no easy alternative way to inject this when executing tests. /// - private static bool SuppressNativeCrash(SentryOptions options, CocoaSdk.SentryEvent evt) + internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt, IHub hub) { + if (hub is DisabledHub) + { + return evt; + } + + // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. + // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK + // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. + // There should only be one exception on the event in this case if ((options.Native.SuppressSignalAborts || options.Native.SuppressExcBadAccess) && evt.Exceptions?.Length == 1) { @@ -219,7 +224,7 @@ private static bool SuppressNativeCrash(SentryOptions options, CocoaSdk.SentryEv // Don't send it options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return true; + return null!; } // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the @@ -230,33 +235,10 @@ private static bool SuppressNativeCrash(SentryOptions options, CocoaSdk.SentryEv // Don't send it options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return true; + return null!; } } - return false; - } - - /// - /// This overload allows us to inject an IHub for testing. During normal execution, the CurrentHub is used. - /// However, since this class is static, there's no easy alternative way to inject this when executing tests. - /// - internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt, IHub hub) - { - // Redundant native crash events must be suppressed even if the SDK is - // disabled (or not yet fully initialized) to avoid sending duplicates. - // https://github.com/getsentry/sentry-dotnet/pull/4521#discussion_r2347616896 - if (SuppressNativeCrash(options, evt)) - { - return null!; - } - - // If the SDK is disabled, there are no event processors or before send to run. - if (hub is DisabledHub) - { - return evt; - } - // We run our SIGABRT checks first before running managed processors. // Because we delegate to user code, we need to catch/log exceptions. try diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 5faa149720..582112fc4c 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -322,25 +322,14 @@ private static Dictionary LoadDebugImagesOnce(IDiagnosticLogge [DllImport("sentry-native")] internal static extern void sentry_value_decref(sentry_value_t value); - // Mirrors the native `sentry_value_t` union (uint64_t or double). - // Implemented with a single ulong backing field and BitConverter - // to reinterpret values, since explicit unions cause issues with - // Blazor WASM interop generators. + // native union sentry_value_u/t + [StructLayout(LayoutKind.Explicit)] internal struct sentry_value_t { - private ulong _bits; - - internal ulong Bits - { - readonly get => _bits; - set => _bits = value; - } - - internal double Double - { - readonly get => BitConverter.UInt64BitsToDouble(_bits); - set => _bits = BitConverter.DoubleToUInt64Bits(value); - } + [FieldOffset(0)] + internal ulong _bits; + [FieldOffset(0)] + internal double _double; } [DllImport("sentry-native")] diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 850b3840d8..e4c92b5e50 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -113,13 +113,13 @@ <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - - - - - + + + + + + + diff --git a/src/Sentry/buildTransitive/Sentry.targets b/src/Sentry/buildTransitive/Sentry.targets index c33ed0efbf..653cc294c0 100644 --- a/src/Sentry/buildTransitive/Sentry.targets +++ b/src/Sentry/buildTransitive/Sentry.targets @@ -18,7 +18,6 @@ Sentry.Attributes$(MSBuildProjectExtension.Replace('proj', '')) - $([System.Guid]::NewGuid()) true @@ -126,14 +125,11 @@ $(SentrySetCommitReleaseOptions) --org $(SentryOrg) $(SentrySetCommitReleaseOptions) --project $(SentryProject) - <_SentryCLIProGuardOptions Condition="'$(SentryProGuardUUID)' != ''">$(_SentryCLIProGuardOptions) --uuid "$(SentryProGuardUUID)" - <_SentryCLIProGuardOptions Condition="'$(_SentryCLIProGuardOptions.Trim())' != ''">$(_SentryCLIProGuardOptions.Trim()) - $(SentryCLIUploadOptions) --org $(SentryOrg) $(SentryCLIUploadOptions) --project $(SentryProject) $(SentryCLIBaseCommand) debug-files upload $(SentryCLIDebugFilesUploadCommand) $(SentryCLIUploadOptions.Trim()) - $(SentryCLIBaseCommand) upload-proguard $(_SentryCLIProGuardOptions) + $(SentryCLIBaseCommand) upload-proguard $(SentryCLIProGuardMappingUploadCommand) $(SentryCLIUploadOptions.Trim()) @@ -221,7 +217,7 @@ $(SentryCLIUploadDirectory) - $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/*.pdb + $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/$(AssemblyName).pdb $(SentryCLIUploadItems) @(AndroidNativeSymbolFilesExceptDll -> '%(Identity)', ' ') @@ -271,22 +267,9 @@ - - - - - <_Parameter1>io.sentry.proguard-uuid - $(SentryProGuardUUID) - - - - - - + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 301d759ba4..2060397401 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -67,7 +67,7 @@ - + diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 9c5fe1eca0..e63b97a20b 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -1041,47 +1041,6 @@ public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ProcessOnBeforeSend_NativeErrorSuppressionBeforeHubInit(bool suppressNativeErrors) - { - // Arrange - var options = new SentryOptions - { - Dsn = ValidDsn, - DiagnosticLogger = _logger, - IsGlobalModeEnabled = true, - Debug = true, - AutoSessionTracking = false, - BackgroundWorker = Substitute.For(), - InitNativeSdks = false, - }; - options.Native.SuppressExcBadAccess = suppressNativeErrors; - - var scope = new Scope(options); - // `SIGABRT` must be filtered out early on startup during - // the Cocoa SDK init before the Hub instance has been created - var hub = DisabledHub.Instance; - - var evt = new Sentry.CocoaSdk.SentryEvent(); - var ex = new Sentry.CocoaSdk.SentryException("Not checked", "EXC_BAD_ACCESS"); - evt.Exceptions = [ex]; - - // Act - var result = SentrySdk.ProcessOnBeforeSend(options, evt, hub); - - // Assert - if (suppressNativeErrors) - { - result.Should().BeNull(); - } - else - { - result.Exceptions.First().Type.Should().Be("EXC_BAD_ACCESS"); - } - } - [Fact] public void ProcessOnBeforeSend_OptionsBeforeOnSendRuns() { From 61254ed7112a15a8db91c803b5e53ecb7b701559 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 1 Oct 2025 12:25:57 +1300 Subject: [PATCH 7/8] Fix accidental merge changes --- .github/actions/environment/action.yml | 10 +- .github/workflows/build.yml | 6 +- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/device-tests-android.yml | 37 ++++- .github/workflows/device-tests-ios.yml | 1 + .github/workflows/release.yml | 2 +- CHANGELOG.md | 13 ++ integration-test/cli.Tests.ps1 | 7 + scripts/device-test.ps1 | 4 +- scripts/generate-cocoa-bindings.ps1 | 3 - src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 13 +- .../PrivateApiDefinitions.cs | 7 - .../Sentry.Bindings.Cocoa.csproj | 2 + .../SwiftApiDefinitions.cs | 132 ++++++++++++++++++ .../SwiftStructsAndEnums.cs | 9 ++ src/Sentry/Platforms/Cocoa/SentrySdk.cs | 46 ++++-- src/Sentry/Platforms/Native/CFunctions.cs | 23 ++- src/Sentry/Sentry.csproj | 14 +- src/Sentry/buildTransitive/Sentry.targets | 29 +++- test/Directory.Build.props | 2 +- test/Sentry.Tests/SentrySdkTests.cs | 41 ++++++ 21 files changed, 343 insertions(+), 62 deletions(-) diff --git a/.github/actions/environment/action.yml b/.github/actions/environment/action.yml index 2ade97d2cf..1d8f925ccc 100644 --- a/.github/actions/environment/action.yml +++ b/.github/actions/environment/action.yml @@ -73,7 +73,7 @@ runs: distribution: ${{ runner.os == 'Windows' && runner.arch == 'ARM64' && 'microsoft' || 'temurin' }} java-version: '11' - - name: Install Mono + - name: Install Mono (macOS) if: runner.os == 'macOS' shell: bash # Attempt to install Mono, allowing it to fail silently @@ -82,6 +82,14 @@ runs: brew install mono || true brew link --overwrite mono + - name: Install Mono (Ubuntu) + if: ${{ runner.os == 'Linux' && !matrix.container }} + shell: bash + run: | + sudo apt install -y mono-devel + # restore perms for actions/setup-dotnet + sudo chmod -R a+rw /usr/share/dotnet + - name: Install .NET SDK uses: actions/setup-dotnet@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 42cc97a3e5..435e61fd07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 # Pin ubuntu to ensure mono is installed + - os: ubuntu-22.04 rid: linux-x64 - os: ubuntu-22.04-arm rid: linux-arm64 @@ -78,7 +78,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 # Pin ubuntu to ensure mono is installed + - os: ubuntu-22.04 rid: linux-x64 slnf: Sentry-CI-Build-Linux.slnf - os: ubuntu-22.04-arm @@ -362,7 +362,7 @@ jobs: path: src - name: Test AOT - uses: getsentry/github-workflows/sentry-cli/integration-test/@v2 + uses: getsentry/github-workflows/sentry-cli/integration-test/@a5e409bd5bad4c295201cdcfe862b17c50b29ab7 # v2.14.1 env: RuntimeIdentifier: ${{ matrix.rid }} with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index abbea520cf..225ac38054 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: uses: ./.github/actions/environment - name: Initialize CodeQL - uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # pin@v2 + uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # pin@v2 with: languages: csharp @@ -49,6 +49,6 @@ jobs: run: dotnet build Sentry-CI-CodeQL.slnf --no-restore --nologo - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # pin@v2 + uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # pin@v2 with: category: '/language:csharp' diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index 62c85e5eeb..b234d39df2 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -8,6 +8,7 @@ on: pull_request: paths-ignore: - "**.md" + workflow_dispatch: jobs: build: @@ -64,6 +65,12 @@ jobs: env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_NOLOGO: 1 + # We don't need the Google APIs, but the default images are not available for 32+ + ANDROID_EMULATOR_TARGET: google_apis + ANDROID_EMULATOR_RAM_SIZE: 2048M + ANDROID_EMULATOR_ARCH: x86_64 + ANDROID_EMULATOR_DISK_SIZE: 4096M + ANDROID_EMULATOR_OPTIONS: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none steps: # See https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/ - name: Enable KVM group perms @@ -82,22 +89,38 @@ jobs: path: bin - name: Setup Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # pin@v3 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # pin@v3 # Cached AVD setup per https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md - name: Run Tests + id: first-run + continue-on-error: true timeout-minutes: 40 uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 with: api-level: ${{ matrix.api-level }} - # We don't need the Google APIs, but the default images are not available for 32+ - target: google_apis + target: ${{ env.ANDROID_EMULATOR_TARGET }} force-avd-creation: false - ram-size: 2048M - arch: x86_64 - disk-size: 4096M - emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} + arch: ${{ env.ANDROID_EMULATOR_ARCH }} + disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} + emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} + disable-animations: false + script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} + + - name: Retry Tests (if previous failed to run) + if: steps.first-run.outcome == 'failure' + timeout-minutes: 40 + uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0 + with: + api-level: ${{ matrix.api-level }} + target: ${{ env.ANDROID_EMULATOR_TARGET }} + force-avd-creation: false + ram-size: ${{ env.ANDROID_EMULATOR_RAM_SIZE }} + arch: ${{ env.ANDROID_EMULATOR_ARCH }} + disk-size: ${{ env.ANDROID_EMULATOR_DISK_SIZE }} + emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} disable-animations: false script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }} diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index c56226bdb6..0e118745cc 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -8,6 +8,7 @@ on: pull_request: paths-ignore: - "**.md" + workflow_dispatch: jobs: ios-tests: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f9b66c130..6cc72a9ad8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Get auth token id: token - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index aeae0d02c6..80f97e4dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,19 @@ - Fail when building Blazor WASM with Profiling. We don't support profiling in Blazor WebAssembly projects. ([#4512](https://github.com/getsentry/sentry-dotnet/pull/4512)) - Do not overwrite user IP if it is set manually in ASP.NET sdk ([#4513](https://github.com/getsentry/sentry-dotnet/pull/4513)) +- Fix `SentryOptions.Native.SuppressSignalAborts` and `SuppressExcBadAccess` on iOS ([#4521](https://github.com/getsentry/sentry-dotnet/pull/4521)) + +### Dependencies + +- Bump Cocoa SDK from v8.55.1 to v8.56.0 ([#4528](https://github.com/getsentry/sentry-dotnet/pull/4528)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8560) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.55.1...8.56.0) +- Bump CLI from v2.53.0 to v2.54.0 ([#4541](https://github.com/getsentry/sentry-dotnet/pull/4541)) + - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2540) + - [diff](https://github.com/getsentry/sentry-cli/compare/2.53.0...2.54.0) +- Bump Native SDK from v0.10.1 to v0.11.0 ([#4542](https://github.com/getsentry/sentry-dotnet/pull/4542)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0110) + - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.0) ## 5.15.0 diff --git a/integration-test/cli.Tests.ps1 b/integration-test/cli.Tests.ps1 index 44a203a5a6..80612d2178 100644 --- a/integration-test/cli.Tests.ps1 +++ b/integration-test/cli.Tests.ps1 @@ -174,6 +174,13 @@ Describe 'MAUI ()' -ForEach @( 'libxamarin-dotnet.dylib', 'maui-app', 'maui-app.pdb', + 'Microsoft.iOS.pdb', + 'Microsoft.Maui.Controls.Compatibility.pdb', + 'Microsoft.Maui.Controls.pdb', + 'Microsoft.Maui.Controls.Xaml.pdb', + 'Microsoft.Maui.Essentials.pdb', + 'Microsoft.Maui.Graphics.pdb', + 'Microsoft.Maui.pdb', 'Sentry' ) $nonZeroNumberRegex = '[1-9][0-9]*'; diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index 7822b81582..f9d5c30868 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -86,8 +86,8 @@ try { if (!(Get-Command xharness -ErrorAction SilentlyContinue)) { - Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/temp') - dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25412.1' ` + Push-Location ($CI ? $env:RUNNER_TEMP : $IsWindows ? $env:TMP : $IsMacos ? $env:TMPDIR : '/tmp') + dotnet tool install Microsoft.DotNet.XHarness.CLI --global --version '10.0.0-prerelease.25466.1' ` --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json Pop-Location } diff --git a/scripts/generate-cocoa-bindings.ps1 b/scripts/generate-cocoa-bindings.ps1 index 04d44efeb6..eb1295808e 100644 --- a/scripts/generate-cocoa-bindings.ps1 +++ b/scripts/generate-cocoa-bindings.ps1 @@ -278,9 +278,6 @@ $Text = $Text -replace '([\[,] )iOS \(', '$1Introduced (PlatformName.iOS, ' # Make interface partial if we need to access private APIs. Other parts will be defined in PrivateApiDefinitions.cs $Text = $Text -replace '(?m)^interface SentryScope', 'partial $&' -# Prefix SentryBreadcrumb.Serialize and SentryScope.Serialize with new (since these hide the base method) -$Text = $Text -replace '(?m)(^\s*\/\/[^\r\n]*$\s*\[Export \("serialize"\)\]$\s*)(NSDictionary)', '${1}new $2' - $Text = $Text -replace '.*SentryEnvelope .*?[\s\S]*?\n\n', '' $Text = $Text -replace '.*typedef.*SentryOnAppStartMeasurementAvailable.*?[\s\S]*?\n\n', '' diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 31997fdfd7..f33b1c6815 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -225,7 +225,7 @@ interface SentryBreadcrumb : SentrySerializable // -(NSDictionary * _Nonnull)serialize; [Export ("serialize")] - new NSDictionary Serialize(); + NSDictionary Serialize(); // -(BOOL)isEqualToBreadcrumb:(SentryBreadcrumb * _Nonnull)breadcrumb; [Export ("isEqualToBreadcrumb:")] @@ -930,6 +930,11 @@ interface SentrySpan : SentrySerializable [Abstract] [NullAllowed, Export ("baggageHttpHeader")] string BaggageHttpHeader { get; } + + // @required -(NSDictionary * _Nonnull)serialize; + [Abstract] + [Export ("serialize")] + NSDictionary Serialize(); } // @interface SentryHub : NSObject @@ -1494,6 +1499,10 @@ interface SentryOptions [Export ("attachScreenshot")] bool AttachScreenshot { get; set; } + // @property (nonatomic, strong) SentryViewScreenshotOptions * _Nonnull screenshot; + [Export ("screenshot", ArgumentSemantic.Strong)] + SentryViewScreenshotOptions Screenshot { get; set; } + // @property (assign, nonatomic) BOOL attachViewHierarchy; [Export ("attachViewHierarchy")] bool AttachViewHierarchy { get; set; } @@ -1864,7 +1873,7 @@ partial interface SentryScope : SentrySerializable // -(NSDictionary * _Nonnull)serialize; [Export ("serialize")] - new NSDictionary Serialize(); + NSDictionary Serialize(); // -(void)setContextValue:(NSDictionary * _Nonnull)value forKey:(NSString * _Nonnull)key __attribute__((swift_name("setContext(value:key:)"))); [Export ("setContextValue:forKey:")] diff --git a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs index ccd5d1865d..55f59db3fe 100644 --- a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs @@ -17,13 +17,6 @@ partial interface SentryScope // The following types are type-forwarded in various public headers, but have no headers of their own. // Generate stub classes so the APIs that use them can still operate. -[Internal] -[DisableDefaultCtor] -[BaseType (typeof(NSObject))] -interface SentrySession -{ -} - [Internal] [DisableDefaultCtor] [BaseType (typeof(NSObject))] diff --git a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj index 9607318492..10b3899977 100644 --- a/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj +++ b/src/Sentry.Bindings.Cocoa/Sentry.Bindings.Cocoa.csproj @@ -12,6 +12,8 @@ $([System.IO.File]::ReadAllText("$(MSBuildThisFileDirectory)../../modules/sentry-cocoa.properties")) $([System.Text.RegularExpressions.Regex]::Match($(SentryCocoaProperties), 'version\s*=\s*([^\s]+)').Groups[1].Value) $(SentryCocoaCache)Sentry-$(SentryCocoaVersion).xcframework + + $(NoWarn);CS0108 diff --git a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs index a009e340ec..1bf7b8ded4 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftApiDefinitions.cs @@ -553,6 +553,103 @@ interface SentrySDK void ClearLogger (); } +// @interface SentrySession : NSObject +[BaseType (typeof(NSObject), Name = "_TtC6Sentry13SentrySession")] +[DisableDefaultCtor] +[Internal] +interface SentrySession +{ + // -(instancetype _Nonnull)initWithReleaseName:(NSString * _Nonnull)releaseName distinctId:(NSString * _Nonnull)distinctId __attribute__((objc_designated_initializer)); + [Export ("initWithReleaseName:distinctId:")] + [DesignatedInitializer] + NativeHandle Constructor (string releaseName, string distinctId); + + // -(instancetype _Nullable)initWithJSONObject:(NSDictionary * _Nonnull)jsonObject __attribute__((objc_designated_initializer)); + [Export ("initWithJSONObject:")] + [DesignatedInitializer] + NativeHandle Constructor (NSDictionary jsonObject); + + // -(void)endSessionExitedWithTimestamp:(NSDate * _Nonnull)timestamp; + [Export ("endSessionExitedWithTimestamp:")] + void EndSessionExitedWithTimestamp (NSDate timestamp); + + // -(void)endSessionCrashedWithTimestamp:(NSDate * _Nonnull)timestamp; + [Export ("endSessionCrashedWithTimestamp:")] + void EndSessionCrashedWithTimestamp (NSDate timestamp); + + // -(void)endSessionAbnormalWithTimestamp:(NSDate * _Nonnull)timestamp; + [Export ("endSessionAbnormalWithTimestamp:")] + void EndSessionAbnormalWithTimestamp (NSDate timestamp); + + // -(void)incrementErrors; + [Export ("incrementErrors")] + void IncrementErrors (); + + // @property (readonly, copy, nonatomic) NSUUID * _Nonnull sessionId; + [Export ("sessionId", ArgumentSemantic.Copy)] + NSUuid SessionId { get; } + + // @property (readonly, copy, nonatomic) NSDate * _Nonnull started; + [Export ("started", ArgumentSemantic.Copy)] + NSDate Started { get; } + + // @property (readonly, nonatomic) enum SentrySessionStatus status; + [Export ("status")] + SentrySessionStatus Status { get; } + + // @property (nonatomic) NSUInteger errors; + [Export ("errors")] + nuint Errors { get; set; } + + // @property (readonly, nonatomic) NSUInteger sequence; + [Export ("sequence")] + nuint Sequence { get; } + + // @property (readonly, copy, nonatomic) NSString * _Nonnull distinctId; + [Export ("distinctId")] + string DistinctId { get; } + + // @property (readonly, nonatomic, strong) NSNumber * _Nullable flagInit; + [NullAllowed, Export ("flagInit", ArgumentSemantic.Strong)] + NSNumber FlagInit { get; } + + // @property (readonly, copy, nonatomic) NSDate * _Nullable timestamp; + [NullAllowed, Export ("timestamp", ArgumentSemantic.Copy)] + NSDate Timestamp { get; } + + // @property (readonly, nonatomic, strong) NSNumber * _Nullable duration; + [NullAllowed, Export ("duration", ArgumentSemantic.Strong)] + NSNumber Duration { get; } + + // @property (readonly, copy, nonatomic) NSString * _Nullable releaseName; + [NullAllowed, Export ("releaseName")] + string ReleaseName { get; } + + // @property (copy, nonatomic) NSString * _Nullable environment; + [NullAllowed, Export ("environment")] + string Environment { get; set; } + + // @property (nonatomic, strong) SentryUser * _Nullable user; + [NullAllowed, Export ("user", ArgumentSemantic.Strong)] + SentryUser User { get; set; } + + // @property (copy, nonatomic) NSString * _Nullable abnormalMechanism; + [NullAllowed, Export ("abnormalMechanism")] + string AbnormalMechanism { get; set; } + + // -(NSDictionary * _Nonnull)serialize __attribute__((warn_unused_result(""))); + [Export ("serialize")] + NSDictionary Serialize(); + + // -(void)setFlagInit; + [Export ("setFlagInit")] + void SetFlagInit (); + + // -(id _Nonnull)copyWithZone:(struct _NSZone * _Nullable)zone __attribute__((warn_unused_result(""))); + // [Export ("copyWithZone:")] + // unsafe NSObject CopyWithZone ([NullAllowed] _NSZone* zone); +} + // @interface SentryUserFeedback : NSObject [BaseType(typeof(NSObject))] [DisableDefaultCtor] @@ -747,6 +844,41 @@ interface SentryUserFeedbackWidgetConfiguration // IntPtr Constructor(); // } +// @interface SentryViewScreenshotOptions : NSObject +[BaseType (typeof(NSObject), Name = "_TtC6Sentry27SentryViewScreenshotOptions")] +[Internal] +interface SentryViewScreenshotOptions //: ISentryRedactOptions +{ + // @property (nonatomic) BOOL enableViewRendererV2; + [Export ("enableViewRendererV2")] + bool EnableViewRendererV2 { get; set; } + + // @property (nonatomic) BOOL enableFastViewRendering; + [Export ("enableFastViewRendering")] + bool EnableFastViewRendering { get; set; } + + // @property (nonatomic) BOOL maskAllImages; + [Export ("maskAllImages")] + bool MaskAllImages { get; set; } + + // @property (nonatomic) BOOL maskAllText; + [Export ("maskAllText")] + bool MaskAllText { get; set; } + + // @property (copy, nonatomic) NSArray * _Nonnull maskedViewClasses; + [Export ("maskedViewClasses", ArgumentSemantic.Copy)] + Class[] MaskedViewClasses { get; set; } + + // @property (copy, nonatomic) NSArray * _Nonnull unmaskedViewClasses; + [Export ("unmaskedViewClasses", ArgumentSemantic.Copy)] + Class[] UnmaskedViewClasses { get; set; } + + // -(instancetype _Nonnull)initWithEnableViewRendererV2:(BOOL)enableViewRendererV2 enableFastViewRendering:(BOOL)enableFastViewRendering maskAllText:(BOOL)maskAllText maskAllImages:(BOOL)maskAllImages maskedViewClasses:(NSArray * _Nonnull)maskedViewClasses unmaskedViewClasses:(NSArray * _Nonnull)unmaskedViewClasses __attribute__((objc_designated_initializer)); + [Export ("initWithEnableViewRendererV2:enableFastViewRendering:maskAllText:maskAllImages:maskedViewClasses:unmaskedViewClasses:")] + [DesignatedInitializer] + NativeHandle Constructor (bool enableViewRendererV2, bool enableFastViewRendering, bool maskAllText, bool maskAllImages, Class[] maskedViewClasses, Class[] unmaskedViewClasses); +} + // @protocol SentryViewScreenshotProvider [Protocol (Name = "_TtP6Sentry28SentryViewScreenshotProvider_")] [Model] diff --git a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs index 806daffd87..1453151744 100644 --- a/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs +++ b/src/Sentry.Bindings.Cocoa/SwiftStructsAndEnums.cs @@ -69,6 +69,15 @@ internal enum SentryRRWebEventType : long Custom = 5 } +[Native] +internal enum SentrySessionStatus : ulong +{ + Ok = 0, + Exited = 1, + Crashed = 2, + Abnormal = 3 +} + [Native] internal enum SentryTransactionNameSource : long { diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 9cd167dc0e..32dd3e40e0 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -195,20 +195,15 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( => ProcessOnBeforeSend(options, evt, CurrentHub); /// - /// This overload allows us to inject an IHub for testing. During normal execution, the CurrentHub is used. - /// However, since this class is static, there's no easy alternative way to inject this when executing tests. + /// Apply suppression logic for redundant native `SIGABRT` and `EXC_BAD_ACCESS` crash events + /// that have already been captured as managed exceptions by the Sentry.NET SDK to avoid sending + /// duplicate events to Sentry - once managed and once native. + /// + /// The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK + /// But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. /// - internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt, IHub hub) + private static bool SuppressNativeCrash(SentryOptions options, CocoaSdk.SentryEvent evt) { - if (hub is DisabledHub) - { - return evt; - } - - // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. - // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK - // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. - // There should only be one exception on the event in this case if ((options.Native.SuppressSignalAborts || options.Native.SuppressExcBadAccess) && evt.Exceptions?.Length == 1) { @@ -224,7 +219,7 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // Don't send it options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; + return true; } // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the @@ -235,10 +230,33 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // Don't send it options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; + return true; } } + return false; + } + + /// + /// This overload allows us to inject an IHub for testing. During normal execution, the CurrentHub is used. + /// However, since this class is static, there's no easy alternative way to inject this when executing tests. + /// + internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt, IHub hub) + { + // Redundant native crash events must be suppressed even if the SDK is + // disabled (or not yet fully initialized) to avoid sending duplicates. + // https://github.com/getsentry/sentry-dotnet/pull/4521#discussion_r2347616896 + if (SuppressNativeCrash(options, evt)) + { + return null!; + } + + // If the SDK is disabled, there are no event processors or before send to run. + if (hub is DisabledHub) + { + return evt; + } + // We run our SIGABRT checks first before running managed processors. // Because we delegate to user code, we need to catch/log exceptions. try diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 582112fc4c..5faa149720 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -322,14 +322,25 @@ private static Dictionary LoadDebugImagesOnce(IDiagnosticLogge [DllImport("sentry-native")] internal static extern void sentry_value_decref(sentry_value_t value); - // native union sentry_value_u/t - [StructLayout(LayoutKind.Explicit)] + // Mirrors the native `sentry_value_t` union (uint64_t or double). + // Implemented with a single ulong backing field and BitConverter + // to reinterpret values, since explicit unions cause issues with + // Blazor WASM interop generators. internal struct sentry_value_t { - [FieldOffset(0)] - internal ulong _bits; - [FieldOffset(0)] - internal double _double; + private ulong _bits; + + internal ulong Bits + { + readonly get => _bits; + set => _bits = value; + } + + internal double Double + { + readonly get => BitConverter.UInt64BitsToDouble(_bits); + set => _bits = BitConverter.DoubleToUInt64Bits(value); + } } [DllImport("sentry-native")] diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index e4c92b5e50..850b3840d8 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -113,13 +113,13 @@ <_OSArchitecture>$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - - - - - + + + + + + + diff --git a/src/Sentry/buildTransitive/Sentry.targets b/src/Sentry/buildTransitive/Sentry.targets index 653cc294c0..c33ed0efbf 100644 --- a/src/Sentry/buildTransitive/Sentry.targets +++ b/src/Sentry/buildTransitive/Sentry.targets @@ -18,6 +18,7 @@ Sentry.Attributes$(MSBuildProjectExtension.Replace('proj', '')) + $([System.Guid]::NewGuid()) true @@ -125,11 +126,14 @@ $(SentrySetCommitReleaseOptions) --org $(SentryOrg) $(SentrySetCommitReleaseOptions) --project $(SentryProject) + <_SentryCLIProGuardOptions Condition="'$(SentryProGuardUUID)' != ''">$(_SentryCLIProGuardOptions) --uuid "$(SentryProGuardUUID)" + <_SentryCLIProGuardOptions Condition="'$(_SentryCLIProGuardOptions.Trim())' != ''">$(_SentryCLIProGuardOptions.Trim()) + $(SentryCLIUploadOptions) --org $(SentryOrg) $(SentryCLIUploadOptions) --project $(SentryProject) $(SentryCLIBaseCommand) debug-files upload $(SentryCLIDebugFilesUploadCommand) $(SentryCLIUploadOptions.Trim()) - $(SentryCLIBaseCommand) upload-proguard + $(SentryCLIBaseCommand) upload-proguard $(_SentryCLIProGuardOptions) $(SentryCLIProGuardMappingUploadCommand) $(SentryCLIUploadOptions.Trim()) @@ -217,7 +221,7 @@ $(SentryCLIUploadDirectory) - $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/$(AssemblyName).pdb + $(SentryCLIUploadItems) $(IntermediateOutputPath)linked/*.pdb $(SentryCLIUploadItems) @(AndroidNativeSymbolFilesExceptDll -> '%(Identity)', ' ') @@ -267,9 +271,22 @@ - - + + + + + <_Parameter1>io.sentry.proguard-uuid + $(SentryProGuardUUID) + + + + + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 2060397401..301d759ba4 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -67,7 +67,7 @@ - + diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index e63b97a20b..9c5fe1eca0 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -1041,6 +1041,47 @@ public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors } } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ProcessOnBeforeSend_NativeErrorSuppressionBeforeHubInit(bool suppressNativeErrors) + { + // Arrange + var options = new SentryOptions + { + Dsn = ValidDsn, + DiagnosticLogger = _logger, + IsGlobalModeEnabled = true, + Debug = true, + AutoSessionTracking = false, + BackgroundWorker = Substitute.For(), + InitNativeSdks = false, + }; + options.Native.SuppressExcBadAccess = suppressNativeErrors; + + var scope = new Scope(options); + // `SIGABRT` must be filtered out early on startup during + // the Cocoa SDK init before the Hub instance has been created + var hub = DisabledHub.Instance; + + var evt = new Sentry.CocoaSdk.SentryEvent(); + var ex = new Sentry.CocoaSdk.SentryException("Not checked", "EXC_BAD_ACCESS"); + evt.Exceptions = [ex]; + + // Act + var result = SentrySdk.ProcessOnBeforeSend(options, evt, hub); + + // Assert + if (suppressNativeErrors) + { + result.Should().BeNull(); + } + else + { + result.Exceptions.First().Type.Should().Be("EXC_BAD_ACCESS"); + } + } + [Fact] public void ProcessOnBeforeSend_OptionsBeforeOnSendRuns() { From 9faa32ee4fafcb97cd4ab0b396236ffa551a5d6c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 1 Oct 2025 12:32:46 +1300 Subject: [PATCH 8/8] . --- CHANGELOG.md | 10 +++++----- Directory.Build.props | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f97e4dd8..8d608379d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,13 +49,13 @@ ### Dependencies - Bump Cocoa SDK from v8.55.1 to v8.56.0 ([#4528](https://github.com/getsentry/sentry-dotnet/pull/4528)) - - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8560) - - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.55.1...8.56.0) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8560) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.55.1...8.56.0) - Bump CLI from v2.53.0 to v2.54.0 ([#4541](https://github.com/getsentry/sentry-dotnet/pull/4541)) - - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2540) - - [diff](https://github.com/getsentry/sentry-cli/compare/2.53.0...2.54.0) + - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2540) + - [diff](https://github.com/getsentry/sentry-cli/compare/2.53.0...2.54.0) - Bump Native SDK from v0.10.1 to v0.11.0 ([#4542](https://github.com/getsentry/sentry-dotnet/pull/4542)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0110) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0110) - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.0) ## 5.15.0 diff --git a/Directory.Build.props b/Directory.Build.props index 8214fd4076..c5ca1758a8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -102,7 +102,7 @@ - 2.53.0 + 2.55.0 $(MSBuildThisFileDirectory)tools\sentry-cli\$(SentryCLIVersion)\