Repository navigation
Inconsistent behavior and segfaults on Android Release build #129496
Description
Activity
- addeduntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner
on Jun 17, 2026 Here I found a discussion suggesting that
ArrayPool<T>.Shared.Rent()as used in SplitSpaces comes with a few caveats.Scratch my concerns about the lazy-initialized Element.ClassList or SplitSpaces renting its buffer array from a pool.
I was able to rule out both as the reason for the weird Android Release build behavior.I've tried disabling trimming, AOT and optimizations - neither resolves the issue.
<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <PublishTrimmed>false</PublishTrimmed> <Optimize>False</Optimize> <RunAOTCompilation>false</RunAOTCompilation> </PropertyGroup>
public static class MauiProgram { public static MauiApp CreateMauiApp() { #if ANDROID && RELEASE System.Environment.SetEnvironmentVariable("MONO_OPTIMIZER", "-all"); #endif var builder = MauiApp.CreateBuilder(); ... return builder.Build(); } }
I was able to rule out further differences between the Android Debug and Release build processes. Both of the following do not resolve the issue in Release:
- skipping assembly bundling using
<AndroidUseAssemblyStore>false</AndroidUseAssemblyStore>as you can read about here - setting
<AndroidLinkMode>None</AndroidLinkMode>as is the default for the Debug configuration
- skipping assembly bundling using
@h0lg any chance you can provide a tombstone?
- addedneeds-author-actionAn issue or pull request that requires more info or actions from the author.An issue or pull request that requires more info or actions from the author.
on Jul 12, 2026 Hey @steveisok , thanks for getting back to me. I'll look into pulling a tombstone.
I have an update as well. Studying the differences between
DebugandReleasein the default build properties for Android (?) I was able to narrow the issue down to two build properties:- AndroidUseInterpreter is
trueby default forDebug,falseforRelease - RunAOTCompilation is
falseby default forDebug,trueforRelease
Flipping both to the
Debugdefaults for theReleaseconfig yields a correctly working AndroidReleasebuild and I'm currently using this workaround configuration:<PropertyGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' And '$(Configuration)' == 'Release'"> <!-- https://learn.microsoft.com/en-us/dotnet/android/building-apps/build-properties#androiduseinterpreter --> <AndroidUseInterpreter>True</AndroidUseInterpreter> <!-- https://learn.microsoft.com/en-us/dotnet/android/building-apps/build-properties#runaotcompilation --> <RunAOTCompilation>false</RunAOTCompilation> </PropertyGroup>
- AndroidUseInterpreter is
- removedneeds-author-actionAn issue or pull request that requires more info or actions from the author.An issue or pull request that requires more info or actions from the author.
on Jul 14, 2026 @steveisok Here's
- a tombstone generated with
- an updated version of the reproduction app
for the test scenario 2 from above: selecting from a full, remote HTML document.
Note that the observed behavior for some of the different flavors of scenario 1 (selecting from a simplified, local document) has changed. It's still not correct and finds 0 instead of the 2 nodes it should find - but at least it consistently fails now instead of the flaky behavior it had before, when it first found 6, then 7 nodes. But that scenario never caused a segfault, so we'll ignore that.
I've talked to a chat bot about this tombstone and some others and it pointed out that the fault addresses are fairly consistent and look odd - like UTF-16 little endian encoded strings instead of like plausible pointers:
0x0067006e00690064 => 0064 0069 006e 0067 => d i n g 0x0067006e00690074 => 0074 0069 006e 0067 => t i n gEdit: It may be worth pointing out here what scenario 2 effectively does - because the class selector ends in ding:
var doc = await ctx.OpenAsync("https://en.wikipedia.org/wiki/Type_variance"); var headings = doc.QuerySelectorAll(".mw-heading");
Here's the start of another tombstone with the 2nd (ting) fault address:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Build fingerprint: 'google/akita/akita:16/CP1A.260505.005/15081906:user/release-keys' Kernel Release: '6.1.145-android14-11-gfa1d6308d1fe-ab14691759' Revision: 'MP1.0' ABI: 'arm64' Timestamp: 2026-07-17 15:12:50.998652840+0200 Process uptime: 35s Executable: /system/bin/app_process64 Cmdline: com.companyname.mauiapp1 pid: 1599, tid: 1599, name: nyname.mauiapp1 >>> com.companyname.mauiapp1 <<< uid: 10373 tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE) pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY) esr: 0000000092000004 (Data Abort Exception 0x24) signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0067006e00690074 (read) x0 000000000000000b x1 0067006e00690064 x2 0067006e00690064 x3 b400007a8c9e9558 x4 0000000000000007 x5 b400007a8c9e9558 x6 0000000000000000 x7 0000000000000000 x8 0000007c6cef73a0 x9 0000000000000001 x10 0000000000000001 x11 000000000000063f x12 0000000000000075 x13 0000007c4f7c1920 x14 0000000000000008 x15 b400007a8d4562e8 x16 00000078770e0460 x17 0000000000000000 x18 0000007c6dbe4000 x19 0000000000000001 x20 0000000000000000 x21 0000000000000000 x22 0000000000000007 x23 0000007900c4b888 x24 0067006e00690064 x25 0000007900c4b538 x26 0067006e00690064 x27 0000000000000000 x28 0000007fd9ef3310 x29 0000007fd9ef2b80 lr 0000007876f9e1c8 sp 0000007fd9ef2b80 pc 0000007876e91f28 pst 0000000080001000 esr 0000000092000004 vg 0000000000000002 1 total frames backtrace: #00 pc 0000000000019f28 /data/app/~~xNBSyDMwZugDXYRAHDeMbQ==/com.companyname.mauiapp1-Kq0IltOn-17MJgrxN3qL-g==/lib/arm64/libaot-System.Private.CoreLib.dll.so memory near x3 ([anon:scudo:primary]): ...Detailed Root Cause Analysis & JIT Bug Behavior
I have successfully traced the exact root cause of this
ArrayTypeMismatchExceptioninsideTokenList.Addon.NET 10 ARM64 Releasebuilds.🔍 Root Cause (Virtual Stub Dispatch / VSD Offset Corruption)
Under ARM64 Release builds, the JIT compiler miscompiles the interface call to
ITokenList.Contains(String token). Due to a bug in VSD or interface devirtualization, it incorrectly resolves the target method slot to the adjacent methodITokenList.Add(String[] tokens)instead.- Type Confusion:
ClassSelector.Matchattempts to executeelement.ClassList.Contains(_cls)passing a singlestringparameter. - Mismatched Execution: Because JIT calls
ITokenList.Addinstead, the singlestringreference pointer is loaded into the register expecting astring[]array reference. - Array Bounds and Store Violations: Inside
Add, it loops over the parameters and callslist.Add(item). Since it treats astringinstance structure as astring[]array, reading element indices yields garbage pointers. When trying to load one of these garbage/mismatched pointers and insert it into the destinationList<string>array, the CLR covariance check (stelem_ref) fails and throwsArrayTypeMismatchExceptioninsideAddWithResize.
🛠️ Proof of Concept Workaround
Bypassing the interface stub dispatch completely resolves the crash. Modifying the caller to use a concrete direct call instead of an interface call prevents VSD miscompilation:
// Inside ClassSelector.Match: var list = element.ClassList; if (list is TokenList concreteList) { // Bypasses VSD interface dispatch, compiles to a direct call -> Works perfectly! return concreteList.Contains(_cls); } return list.Contains(_cls); // JIT-bug prone interface dispatch
This confirms the issue is solely a JIT compiler/dispatch bug on ARM64 when compiling
ITokenListinterface method calls.Reacted by Holger Schmidt and Vlad Brezae- Type Confusion:
dotnet-policy-service commented
on Jul 20, 2026 ContributorMore actionsTagging subscribers to this area: @steveisok, @vitek-karas
See info in area-owners.md if you want to be subscribed.@BrzVlad can you take a quick look please? Mainly is this is something we want to fix in servicing for .NET 10.
- removeduntriagedNew issue has not been triaged by the area ownerNew issue has not been triaged by the area owner
on Jul 22, 2026 - added a commit that references this issue
on Jul 28, 2026 - locked and limited conversation to collaborators
on Aug 30, 2026
Description
I'm running into some strange behavior and segfaults when using AngleSharp on Android Release builds to select elements via their ClassList from a HTML document.
I'm not sure whether this is an .NET Android runtime bug or an AngleSharp incompatibility with it, so I'm filing an issue in both repos. Here's the AngleSharp issue for reference.
Reproduction Steps
Find attached a .Net MAUI app with a reproduction for two cases:
.anodes from a HTML snippetExpected behavior
Actual behavior
On Android Release build I notice that for case 1
IElement.GetAttribute("class")yields the expected nodesQuerySelectorAllorIElement.ClassList.Contains()Selecting nodes from a complete, remote HTML document (case 2) works fine on other builds - but causes the app to crash with a segfault on Android Release:
Regression?
Haven't tried with earlier Fw versions.
Known Workarounds
No response
Configuration
.Net 10, Android, ARM64, Release build
Other information
Not sure whether this is related to the segfault - but on the original app, I was (on occasion, not reliably) able to catch a managed exception with the following stacktrace after something somewhere seemingly warmed up enough:
No clue what triggers the
ArrayTypeMismatchExceptionor whyTokenList.Addappears. I don't see where it could be called from.Maybe the managed stacktrace is mangled due to a prior segfault?
Looking into the AngleSharp source, I notice the following:
ClassSelector.Match()accessesElement.ClassList, which is a lazy-initializedTokenList. That lazy-initialization is not locked to be thread-safe - but without parallelized tasks initializing the ClassList on the same element, this shouldn't be an issue I guess.TokenListctor calls viaTokenList.Updateinto theSplitSpacesextension, implemented usingArrayPool<char>. Borrowed, uncleared arrays sound like something that could cause values to pop up in unexpected places.