Skip to content

Inconsistent behavior and segfaults on Android Release build #129496

Description

@h0lg

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:

  1. Selecting the .a nodes from a HTML snippet
<div>
    <div class='a b c'>oh</div>
    <div class='a b c'>hai</div>
    <div class='d e f'>maui</div>
</div>
  1. Selecting nodes from a complete, remote HTML document

Expected behavior

  1. Like in builds other than Android Release, 2 nodes should be found.
Image
  1. Nodes in a full, remote HTML document should be selected without the app crashing.
Image

Actual behavior

On Android Release build I notice that for case 1

  • only directly checking IElement.GetAttribute("class") yields the expected nodes
  • when using QuerySelectorAll or IElement.ClassList.Contains()
    • the selected nodes are duplicated for some reason
    • nodes that don't match the selector in the result set
    • the selected nodes on the first click are different from those on following selections - even though we select from the same snippet

Image Image

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:

Time	Device Name	Type	PID	Tag	Message
06-17 01:48:49.598	Google Pixel 8a	Info	1838	DropBoxManagerService	add tag=data_app_native_crash isTagEnabled=true flags=0x2
  Force finishing activity com.companyname.mauiapp1/crc64e632a077a20c694c.MainActivity
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	backtrace:
      #00 pc 000000000000bbac  <anonymous:7c649b9000>
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	1 total frames
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0067006e00690064 (read)
    x0  b400007a8c996b18  x1  0000000000000000  x2  0067006e00690064  x3  b400007a8ca425d0
    x4  0000000000000007  x5  b400007a8c9e5168  x6  b400007a9ca37f50  x7  b400007a9ca37f50
    x8  0000000000000000  x9  0000000000000000  x10 0000000000000008  x11 0000000000000004
    x12 0000000000000029  x13 0000007c41df0274  x14 0000000000000008  x15 b400007a8d760f98
    x16 0000007c649c4b48  x17 0000007c649c4b84  x18 0000007c6dbe4000  x19 0000000000000000
    x20 0000007864beb8b0  x21 0000007900bbb0f0  x22 0000000000000000  x23 0000000000000000
    x24 0000007900bbb190  x25 0000007900bbb1b0  x26 0067006e00690064  x27 0000000000000000
    x28 0000007fd9ef3310  x29 0000007fd9ef2c40
    lr  00000078793ac730  sp  0000007fd9ef2c40  pc  0000007c649c4bac  pst 0000000020001000
    esr 0000000092000004  vg  0000000000000002
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	esr: 0000000092000004 (Data Abort Exception 0x24)
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	uid: 10364
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	pid: 10705, tid: 10705, name: nyname.mauiapp1  >>> com.companyname.mauiapp1 <<<
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Cmdline: com.companyname.mauiapp1
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Executable: /system/bin/app_process64
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Process uptime: 8s
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Timestamp: 2026-06-17 01:48:49.426520014+0200
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	ABI: 'arm64'
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Revision: 'MP1.0'
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Kernel Release: '6.1.145-android14-11-gfa1d6308d1fe-ab14691759'
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	Build fingerprint: 'google/akita/akita:16/CP1A.260505.005/15081906:user/release-keys'
06-17 01:48:49.582	Google Pixel 8a	Error	10768	DEBUG	*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
06-17 01:48:49.403	Google Pixel 8a	Info	10768	crash_dump64	performing dump of process 10705 (target tid = 10705)
06-17 01:48:49.401	Google Pixel 8a	Info	828	tombstoned	received crash request for pid 10705
06-17 01:48:49.401	Google Pixel 8a	Info	10768	crash_dump64	obtaining output fd from tombstoned, type: kDebuggerdTombstoneProto
06-17 01:48:49.378	Google Pixel 8a	Info	650	keystore2	system/security/keystore2/watchdog/src/lib.rs:371 - Watchdog thread idle -> terminating. Have a great day.
06-17 01:48:49.202	Google Pixel 8a	Error	10705	libc	Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x67006e00690064 in tid 10705 (nyname.mauiapp1), pid 10705 (nyname.mauiapp1)

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:

System.ArrayTypeMismatchException: Arg_ArrayTypeMismatchException
   at AngleSharp.Dom.TokenList.Add(String[] tokens)
   at AngleSharp.Css.Dom.ClassSelector.Match(IElement element, IElement scope)
   at AngleSharp.Css.Dom.CompoundSelector.Match(IElement element, IElement scope)
   at AngleSharp.Css.Dom.SelectorExtensions.<>c.<MatchAll>b__3_0(INode node, SelectorState state)
   at AngleSharp.Dom.NodeExtensions.<GetDescendantsAndSelf>d__8`1[[AngleSharp.Css.Dom.SelectorExtensions.SelectorState, AngleSharp, Version=1.4.0.0, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea]].MoveNext()
   at AngleSharp.Css.Dom.SelectorExtensions.MatchAll(ISelector selector, IEnumerable`1 elements, IElement scope, List`1 result)
   at AngleSharp.Css.Dom.SelectorExtensions.MatchAll(ISelector selector, IEnumerable`1 elements, IElement scope)
   at AngleSharp.Dom.QueryExtensions.QuerySelectorAll[NodeList](NodeList nodes, String selectorText, INode scopeNode)
   at AngleSharp.Dom.Document.QuerySelectorAll(String selectors)

No clue what triggers the ArrayTypeMismatchException or why TokenList.Add appears. 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() accesses Element.ClassList, which is a lazy-initialized TokenList. 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.
  • the TokenList ctor calls via TokenList.Update into the SplitSpaces extension, implemented using ArrayPool<char>. Borrowed, uncleared arrays sound like something that could cause values to pop up in unexpected places.

Activity

  1. h0lg commented on Jun 20, 2026

    @h0lg
    Author

    Here I found a discussion suggesting that ArrayPool<T>.Shared.Rent() as used in SplitSpaces comes with a few caveats.

  2. h0lg commented on Jun 26, 2026

    @h0lg
    Author

    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.

  3. h0lg commented on Jun 27, 2026

    @h0lg
    Author

    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();
        }
    }
  4. h0lg commented on Jun 28, 2026

    @h0lg
    Author

    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:

  5. steveisok commented on Jul 12, 2026

    @steveisok
    Member

    @h0lg any chance you can provide a tombstone?

  6. added
    needs-author-actionAn issue or pull request that requires more info or actions from the author.
    on Jul 12, 2026
  7. h0lg commented on Jul 14, 2026

    @h0lg
    Author

    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 Debug and Release in the default build properties for Android (?) I was able to narrow the issue down to two build properties:

    Flipping both to the Debug defaults for the Release config yields a correctly working Android Release build 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>
  8. h0lg commented on Jul 17, 2026

    @h0lg
    Author

    @steveisok Here's

    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 g
    

    Edit: 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]): ...
    
  9. DevGitPit commented on Jul 19, 2026

    @DevGitPit

    Detailed Root Cause Analysis & JIT Bug Behavior

    I have successfully traced the exact root cause of this ArrayTypeMismatchException inside TokenList.Add on .NET 10 ARM64 Release builds.

    🔍 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 method ITokenList.Add(String[] tokens) instead.

    1. Type Confusion: ClassSelector.Match attempts to execute element.ClassList.Contains(_cls) passing a single string parameter.
    2. Mismatched Execution: Because JIT calls ITokenList.Add instead, the single string reference pointer is loaded into the register expecting a string[] array reference.
    3. Array Bounds and Store Violations: Inside Add, it loops over the parameters and calls list.Add(item). Since it treats a string instance structure as a string[] array, reading element indices yields garbage pointers. When trying to load one of these garbage/mismatched pointers and insert it into the destination List<string> array, the CLR covariance check (stelem_ref) fails and throws ArrayTypeMismatchException inside AddWithResize.

    🛠️ 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 ITokenList interface method calls.

    See- termux/termux-packages#30480 (comment)

  10. dotnet-policy-service commented on Jul 20, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @steveisok, @vitek-karas
    See info in area-owners.md if you want to be subscribed.

  11. vitek-karas commented on Jul 20, 2026

    @vitek-karas
    Member

    @BrzVlad can you take a quick look please? Mainly is this is something we want to fix in servicing for .NET 10.

  12. self-assigned this
    on Jul 21, 2026
  13. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 22, 2026
  14. added this to the 11.0.0 milestone on Jul 22, 2026
  15. locked and limited conversation to collaborators on Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions