{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-14-aosp-source-releases-are-so-late-developers-disassemble/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "### Description\n\nOn Android 17 builds from September 2026, `wcscspn()` and `wcspbrk()` stop at the first character of the string that is >= U+0100 whenever the reject set has two or more characters that are all < U+0100. The character is reported as a match even though it is not in the set. `wcsspn()`, `wcschr()`, `wcsrchr()`, `wmemchr()` and `wcsstr()` are correct.\n\nAny code that scans wide strings for ASCII delimiters, for example newline or wildcard characters, breaks as soon as the text contains an emoji, CJK, Cyrillic, Greek or Latin Extended character.\n\n## Affected / unaffected\n\n| Device          | Build                                                                               | Result |\n| --------------- | ----------------------------------------------------------------------------------- | ------ |\n| Pixel 7         | google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys                | broken |\n| Pixel 8a        | google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys (patch 2026-09-05) | broken |\n| Nothing Phone 2 | Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01)       | ok     |\n\nThe public android17-release sources (android-17.0.0\\_r1, June 2026) build `wcscspn`/`wcspbrk` from external/llvm-libc, which is a plain nested loop over `wchar_t` and is correct. The code on the September builds is different (see \"Mechanism\"), so the regression was introduced between the June and September releases.\n\n## Reproducer\n\nBuild with the NDK (`aarch64-linux-android30-clang`), push to `/data/local/tmp`, run with `adb shell`:\n\n```c\n#include <stdio.h>\n#include <wchar.h>\n\nint main(void)\n{\n    wchar_t s[] = {'a', 0x1F680, 0x100, 'b', 'x', '\\n', 0};\n    printf(\"wcscspn(s, L\\\"x\\\\n\\\") = %zu (expected 4)\\n\", wcscspn(s, L\"x\\n\"));\n    printf(\"wcspbrk(s, L\\\"x\\\\n\\\") = index %ld (expected 4)\\n\", (long)(wcspbrk(s, L\"x\\n\") - s));\n    printf(\"wcsspn(s, L\\\"a\\\\U0001F680\\\") = %zu (expected 2)\\n\", wcsspn(s, L\"a\\U0001F680\"));\n    return 0;\n}\n```\n\nOutput on CP3A.260905.009:\n\n```\nwcscspn(s, L\"x\\n\") = 1 (expected 4)\nwcspbrk(s, L\"x\\n\") = index 1 (expected 4)\nwcsspn(s, L\"a\\U0001F680\") = 2 (expected 2)\n```\n\nA sweep of every code point from U+0001 to U+10FFFF (skipping surrogates and the two set members) with `wcscspn(L\"<cp>\\n\", L\"x\\n\")` returns 0 instead of 1 for 1,111,808 code points: every one from U+0100 to U+10FFFF. U+0001 to U+00FF are correct.\n\nWhich set shapes trigger it (all measured on the Pixel 8a):\n\n| Reject set                                                        | Result |\n| ----------------------------------------------------------------- | ------ |\n| one character                                                     | ok     |\n| two or more characters, all < U+0100 (e.g. `L\"x\\n\"`, `L\"\\xe9\\n\"`) | broken |\n| two or more characters, at least one >= U+0100                    | ok     |\n| haystack limited to U+0080..U+00FF                                | ok     |\n\n## Mechanism (from the shipped libc.so)\n\nDisassembly of `wcscspn` in `/apex/com.android.runtime/lib64/bionic/libc.so` on CP3A.260905.009 shows three paths selected by the set:\n\n1. One character: a plain compare loop. Correct.\n2. Two or more characters: a 256-byte `bool` table on the stack is filled from the set while the set characters are in 1..0xFF. If a set character >= 0x100 is seen, the function falls back to calling `wcschr()` for each haystack character. Correct.\n3. Otherwise the haystack is scanned against the table:\n\n```\na2564:  ldrb  w8, [x9, x8]        ; table[c]\na2568:  tbnz  w8, #0, a2578       ; in set -> stop (correct)\na256c:  ldr   w8, [x22, #0x4]!    ; next character\na2570:  cmp   w8, #0x100\na2574:  b.lo  a2564               ; c < 0x100 -> keep scanning\na2578:  ...                       ; c >= 0x100 -> STOP, reported as a match\n```\n\nA character >= 0x100 cannot be in a set whose members are all < 0x100, so for `wcscspn` the scan must continue past it. The code stops instead. `wcsspn` has the same structure, and there stopping is the correct answer (the character is not in the keep set), which is why only the complement functions are wrong. `wcspbrk` is implemented on top of this scan and inherits the bug.\n\nThe existing bionic test `TEST(wchar, wcspbrk)` in tests/wchar\\_test.cpp only uses ASCII input, so it does not catch this. A test such as `EXPECT_EQ(3u, wcscspn(L\"aĀb\\n\", L\"x\\n\"))` would.\n\n## Real-world impact\n\nVisual Pinball on Android (libwinevbs, a Unix build of Wine's vbscript engine) stopped loading any table whose VBScript source contains a comment with an emoji after the September update: the lexer uses `wcspbrk(ptr, L\"\\n\\r\")` to skip comments, got the position of the first emoji instead of the line end, and reported \"Invalid character\". `FileSystem.MoveFolder` likewise treated every path containing a character >= U+0100 as a wildcard pattern via `wcspbrk(src, L\"*?><\")`.\n\nWorked around downstream by replacing the calls with plain loops: [vpinball/libwinevbs#41](https://github.com/vpinball/libwinevbs/pull/41) (shipped in [vpinball/vpinball@`e7837322f4`](https://github.com/vpinball/vpinball/commit/e7837322f4)).\n\nFull disassembly of the affected functions and the probe sources are available on request.\n\n### I am using a supported NDK\n\n- I have checked and the NDK I'm using is currently supported\n\n### Affected versions\n\nr29\n",
  "authors": [
    "tharen"
  ],
  "captured_at": "2026-10-14T13:03:36.501235501+00:00",
  "category": "news",
  "content_html": "<h3>Description</h3>\n<p>On Android 17 builds from September 2026, <code>wcscspn()</code> and <code>wcspbrk()</code> stop at the first character of the string that is &gt;= U+0100 whenever the reject set has two or more characters that are all &lt; U+0100. The character is reported as a match even though it is not in the set. <code>wcsspn()</code>, <code>wcschr()</code>, <code>wcsrchr()</code>, <code>wmemchr()</code> and <code>wcsstr()</code> are correct.</p>\n<p>Any code that scans wide strings for ASCII delimiters, for example newline or wildcard characters, breaks as soon as the text contains an emoji, CJK, Cyrillic, Greek or Latin Extended character.</p>\n<h2>Affected / unaffected</h2>\n<table>\n<thead>\n<tr>\n<th>Device</th>\n<th>Build</th>\n<th>Result</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Pixel 7</td>\n<td>google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys</td>\n<td>broken</td>\n</tr>\n<tr>\n<td>Pixel 8a</td>\n<td>google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys (patch 2026-09-05)</td>\n<td>broken</td>\n</tr>\n<tr>\n<td>Nothing Phone 2</td>\n<td>Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01)</td>\n<td>ok</td>\n</tr>\n</tbody>\n</table>\n<p>The public android17-release sources (android-17.0.0_r1, June 2026) build <code>wcscspn</code>/<code>wcspbrk</code> from external/llvm-libc, which is a plain nested loop over <code>wchar_t</code> and is correct. The code on the September builds is different (see &quot;Mechanism&quot;), so the regression was introduced between the June and September releases.</p>\n<h2>Reproducer</h2>\n<p>Build with the NDK (<code>aarch64-linux-android30-clang</code>), push to <code>/data/local/tmp</code>, run with <code>adb shell</code>:</p>\n<pre><code class=\"language-c\"><span class=\"code-snippet\" data-language=\"C\"><span class=\"syntax-source syntax-c\"><span class=\"syntax-meta syntax-preprocessor syntax-include syntax-c\"><span class=\"syntax-keyword syntax-control syntax-import syntax-include syntax-c\">#include</span> <span class=\"syntax-string syntax-quoted syntax-other syntax-lt-gt syntax-include syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&lt;</span>stdio.h<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&gt;</span></span>\n</span><span class=\"syntax-meta syntax-preprocessor syntax-include syntax-c\"><span class=\"syntax-keyword syntax-control syntax-import syntax-include syntax-c\">#include</span> <span class=\"syntax-string syntax-quoted syntax-other syntax-lt-gt syntax-include syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&lt;</span>wchar.h<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&gt;</span></span>\n</span>\n<span class=\"syntax-storage syntax-type syntax-c\">int</span> <span class=\"syntax-meta syntax-function syntax-c\"><span class=\"syntax-entity syntax-name syntax-function syntax-c\">main</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-storage syntax-type syntax-c\">void</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-c\">\n</span><span class=\"syntax-meta syntax-function syntax-c\"><span class=\"syntax-meta syntax-block syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-c\">{</span></span></span><span class=\"syntax-meta syntax-function syntax-c\"><span class=\"syntax-meta syntax-block syntax-c\">\n    wchar_t s<span class=\"syntax-meta syntax-brackets syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-begin syntax-c\">[</span><span class=\"syntax-punctuation syntax-section syntax-brackets syntax-end syntax-c\">]</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-c\">=</span> <span class=\"syntax-meta syntax-block syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-begin syntax-c\">{</span><span class=\"syntax-string syntax-quoted syntax-single syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&#39;</span>a<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&#39;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-constant syntax-numeric syntax-c\">0x1F680</span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-constant syntax-numeric syntax-c\">0x100</span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-single syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&#39;</span>b<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&#39;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-single syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&#39;</span>x<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&#39;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-single syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&#39;</span><span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&#39;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-constant syntax-numeric syntax-c\">0</span><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-c\">}</span></span><span class=\"syntax-punctuation syntax-terminator syntax-c\">;</span>\n    <span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">printf</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>wcscspn(s, L<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>x<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\\\</span>n<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>) = <span class=\"syntax-constant syntax-other syntax-placeholder syntax-c\">%zu</span> (expected 4)<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">wcscspn</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\">s<span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-storage syntax-type syntax-string syntax-c\">L</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>x<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span><span class=\"syntax-punctuation syntax-terminator syntax-c\">;</span>\n    <span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">printf</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>wcspbrk(s, L<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>x<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\\\</span>n<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>) = index <span class=\"syntax-constant syntax-other syntax-placeholder syntax-c\">%ld</span> (expected 4)<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span><span class=\"syntax-storage syntax-type syntax-c\">long</span><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">wcspbrk</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\">s<span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-storage syntax-type syntax-string syntax-c\">L</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>x<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span> <span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-c\">-</span> s<span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span><span class=\"syntax-punctuation syntax-terminator syntax-c\">;</span>\n    <span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">printf</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>wcsspn(s, L<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>a<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\\\</span>U0001F680<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\&quot;</span>) = <span class=\"syntax-constant syntax-other syntax-placeholder syntax-c\">%zu</span> (expected 2)<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\n</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span><span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-support syntax-function syntax-C99 syntax-c\">wcsspn</span><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-begin syntax-c\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\">s<span class=\"syntax-punctuation syntax-separator syntax-c\">,</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-c\"><span class=\"syntax-storage syntax-type syntax-string syntax-c\">L</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-c\">&quot;</span>a<span class=\"syntax-constant syntax-character syntax-escape syntax-c\">\\U0001F680</span><span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-c\">&quot;</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-c\"><span class=\"syntax-meta syntax-group syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-group syntax-end syntax-c\">)</span></span></span><span class=\"syntax-punctuation syntax-terminator syntax-c\">;</span>\n    <span class=\"syntax-keyword syntax-control syntax-c\">return</span> <span class=\"syntax-constant syntax-numeric syntax-c\">0</span><span class=\"syntax-punctuation syntax-terminator syntax-c\">;</span>\n</span></span><span class=\"syntax-meta syntax-function syntax-c\"><span class=\"syntax-meta syntax-block syntax-c\"><span class=\"syntax-punctuation syntax-section syntax-block syntax-end syntax-c\">}</span></span></span>\n</span></span></code></pre>\n<p>Output on CP3A.260905.009:</p>\n<pre><code><span class=\"code-snippet\">wcscspn(s, L&quot;x\\n&quot;) = 1 (expected 4)\nwcspbrk(s, L&quot;x\\n&quot;) = index 1 (expected 4)\nwcsspn(s, L&quot;a\\U0001F680&quot;) = 2 (expected 2)\n</span></code></pre>\n<p>A sweep of every code point from U+0001 to U+10FFFF (skipping surrogates and the two set members) with <code>wcscspn(L&quot;&lt;cp&gt;\\n&quot;, L&quot;x\\n&quot;)</code> returns 0 instead of 1 for 1,111,808 code points: every one from U+0100 to U+10FFFF. U+0001 to U+00FF are correct.</p>\n<p>Which set shapes trigger it (all measured on the Pixel 8a):</p>\n<table>\n<thead>\n<tr>\n<th>Reject set</th>\n<th>Result</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>one character</td>\n<td>ok</td>\n</tr>\n<tr>\n<td>two or more characters, all &lt; U+0100 (e.g. <code>L&quot;x\\n&quot;</code>, <code>L&quot;\\xe9\\n&quot;</code>)</td>\n<td>broken</td>\n</tr>\n<tr>\n<td>two or more characters, at least one &gt;= U+0100</td>\n<td>ok</td>\n</tr>\n<tr>\n<td>haystack limited to U+0080..U+00FF</td>\n<td>ok</td>\n</tr>\n</tbody>\n</table>\n<h2>Mechanism (from the shipped libc.so)</h2>\n<p>Disassembly of <code>wcscspn</code> in <code>/apex/com.android.runtime/lib64/bionic/libc.so</code> on CP3A.260905.009 shows three paths selected by the set:</p>\n<ol>\n<li>One character: a plain compare loop. Correct.</li>\n<li>Two or more characters: a 256-byte <code>bool</code> table on the stack is filled from the set while the set characters are in 1..0xFF. If a set character &gt;= 0x100 is seen, the function falls back to calling <code>wcschr()</code> for each haystack character. Correct.</li>\n<li>Otherwise the haystack is scanned against the table:</li>\n</ol>\n<pre><code><span class=\"code-snippet\">a2564:  ldrb  w8, [x9, x8]        ; table[c]\na2568:  tbnz  w8, #0, a2578       ; in set -&gt; stop (correct)\na256c:  ldr   w8, [x22, #0x4]!    ; next character\na2570:  cmp   w8, #0x100\na2574:  b.lo  a2564               ; c &lt; 0x100 -&gt; keep scanning\na2578:  ...                       ; c &gt;= 0x100 -&gt; STOP, reported as a match\n</span></code></pre>\n<p>A character &gt;= 0x100 cannot be in a set whose members are all &lt; 0x100, so for <code>wcscspn</code> the scan must continue past it. The code stops instead. <code>wcsspn</code> has the same structure, and there stopping is the correct answer (the character is not in the keep set), which is why only the complement functions are wrong. <code>wcspbrk</code> is implemented on top of this scan and inherits the bug.</p>\n<p>The existing bionic test <code>TEST(wchar, wcspbrk)</code> in tests/wchar_test.cpp only uses ASCII input, so it does not catch this. A test such as <code>EXPECT_EQ(3u, wcscspn(L&quot;aĀb\\n&quot;, L&quot;x\\n&quot;))</code> would.</p>\n<h2>Real-world impact</h2>\n<p>Visual Pinball on Android (libwinevbs, a Unix build of Wine's vbscript engine) stopped loading any table whose VBScript source contains a comment with an emoji after the September update: the lexer uses <code>wcspbrk(ptr, L&quot;\\n\\r&quot;)</code> to skip comments, got the position of the first emoji instead of the line end, and reported &quot;Invalid character&quot;. <code>FileSystem.MoveFolder</code> likewise treated every path containing a character &gt;= U+0100 as a wildcard pattern via <code>wcspbrk(src, L&quot;*?&gt;&lt;&quot;)</code>.</p>\n<p>Worked around downstream by replacing the calls with plain loops: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/vpinball/libwinevbs/pull/41\">vpinball/libwinevbs#41</a> (shipped in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/vpinball/vpinball/commit/e7837322f4\">vpinball/vpinball@<code>e7837322f4</code></a>).</p>\n<p>Full disassembly of the affected functions and the probe sources are available on request.</p>\n<h3>I am using a supported NDK</h3>\n<ul>\n<li>I have checked and the NDK I'm using is currently supported</li>\n</ul>\n<h3>Affected versions</h3>\n<p>r29</p>\n",
  "content_markdown": "### Description\n\nOn Android 17 builds from September 2026, `wcscspn()` and `wcspbrk()` stop at the first character of the string that is >= U+0100 whenever the reject set has two or more characters that are all < U+0100. The character is reported as a match even though it is not in the set. `wcsspn()`, `wcschr()`, `wcsrchr()`, `wmemchr()` and `wcsstr()` are correct.\n\nAny code that scans wide strings for ASCII delimiters, for example newline or wildcard characters, breaks as soon as the text contains an emoji, CJK, Cyrillic, Greek or Latin Extended character.\n\n## Affected / unaffected\n\n| Device          | Build                                                                               | Result |\n| --------------- | ----------------------------------------------------------------------------------- | ------ |\n| Pixel 7         | google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys                | broken |\n| Pixel 8a        | google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys (patch 2026-09-05) | broken |\n| Nothing Phone 2 | Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01)       | ok     |\n\nThe public android17-release sources (android-17.0.0\\_r1, June 2026) build `wcscspn`/`wcspbrk` from external/llvm-libc, which is a plain nested loop over `wchar_t` and is correct. The code on the September builds is different (see \"Mechanism\"), so the regression was introduced between the June and September releases.\n\n## Reproducer\n\nBuild with the NDK (`aarch64-linux-android30-clang`), push to `/data/local/tmp`, run with `adb shell`:\n\n```c\n#include <stdio.h>\n#include <wchar.h>\n\nint main(void)\n{\n    wchar_t s[] = {'a', 0x1F680, 0x100, 'b', 'x', '\\n', 0};\n    printf(\"wcscspn(s, L\\\"x\\\\n\\\") = %zu (expected 4)\\n\", wcscspn(s, L\"x\\n\"));\n    printf(\"wcspbrk(s, L\\\"x\\\\n\\\") = index %ld (expected 4)\\n\", (long)(wcspbrk(s, L\"x\\n\") - s));\n    printf(\"wcsspn(s, L\\\"a\\\\U0001F680\\\") = %zu (expected 2)\\n\", wcsspn(s, L\"a\\U0001F680\"));\n    return 0;\n}\n```\n\nOutput on CP3A.260905.009:\n\n```\nwcscspn(s, L\"x\\n\") = 1 (expected 4)\nwcspbrk(s, L\"x\\n\") = index 1 (expected 4)\nwcsspn(s, L\"a\\U0001F680\") = 2 (expected 2)\n```\n\nA sweep of every code point from U+0001 to U+10FFFF (skipping surrogates and the two set members) with `wcscspn(L\"<cp>\\n\", L\"x\\n\")` returns 0 instead of 1 for 1,111,808 code points: every one from U+0100 to U+10FFFF. U+0001 to U+00FF are correct.\n\nWhich set shapes trigger it (all measured on the Pixel 8a):\n\n| Reject set                                                        | Result |\n| ----------------------------------------------------------------- | ------ |\n| one character                                                     | ok     |\n| two or more characters, all < U+0100 (e.g. `L\"x\\n\"`, `L\"\\xe9\\n\"`) | broken |\n| two or more characters, at least one >= U+0100                    | ok     |\n| haystack limited to U+0080..U+00FF                                | ok     |\n\n## Mechanism (from the shipped libc.so)\n\nDisassembly of `wcscspn` in `/apex/com.android.runtime/lib64/bionic/libc.so` on CP3A.260905.009 shows three paths selected by the set:\n\n1. One character: a plain compare loop. Correct.\n2. Two or more characters: a 256-byte `bool` table on the stack is filled from the set while the set characters are in 1..0xFF. If a set character >= 0x100 is seen, the function falls back to calling `wcschr()` for each haystack character. Correct.\n3. Otherwise the haystack is scanned against the table:\n\n```\na2564:  ldrb  w8, [x9, x8]        ; table[c]\na2568:  tbnz  w8, #0, a2578       ; in set -> stop (correct)\na256c:  ldr   w8, [x22, #0x4]!    ; next character\na2570:  cmp   w8, #0x100\na2574:  b.lo  a2564               ; c < 0x100 -> keep scanning\na2578:  ...                       ; c >= 0x100 -> STOP, reported as a match\n```\n\nA character >= 0x100 cannot be in a set whose members are all < 0x100, so for `wcscspn` the scan must continue past it. The code stops instead. `wcsspn` has the same structure, and there stopping is the correct answer (the character is not in the keep set), which is why only the complement functions are wrong. `wcspbrk` is implemented on top of this scan and inherits the bug.\n\nThe existing bionic test `TEST(wchar, wcspbrk)` in tests/wchar\\_test.cpp only uses ASCII input, so it does not catch this. A test such as `EXPECT_EQ(3u, wcscspn(L\"aĀb\\n\", L\"x\\n\"))` would.\n\n## Real-world impact\n\nVisual Pinball on Android (libwinevbs, a Unix build of Wine's vbscript engine) stopped loading any table whose VBScript source contains a comment with an emoji after the September update: the lexer uses `wcspbrk(ptr, L\"\\n\\r\")` to skip comments, got the position of the first emoji instead of the line end, and reported \"Invalid character\". `FileSystem.MoveFolder` likewise treated every path containing a character >= U+0100 as a wildcard pattern via `wcspbrk(src, L\"*?><\")`.\n\nWorked around downstream by replacing the calls with plain loops: [vpinball/libwinevbs#41](https://github.com/vpinball/libwinevbs/pull/41) (shipped in [vpinball/vpinball@`e7837322f4`](https://github.com/vpinball/vpinball/commit/e7837322f4)).\n\nFull disassembly of the affected functions and the probe sources are available on request.\n\n### I am using a supported NDK\n\n- I have checked and the NDK I'm using is currently supported\n\n### Affected versions\n\nr29\n",
  "dateCreated": "2026-10-14T13:03:36.501235501+00:00",
  "date_created": "2026-10-14T06:18:01+00:00",
  "date_modified": "2026-10-14T06:18:01+00:00",
  "date_published": "2026-10-14T06:18:01+00:00",
  "description": "Description On Android 17 builds from September 2026, wcscspn() and wcspbrk() stop at the first character of the string that is >= U+0100 whenever the reject set has two or more characters that are all < U+0100. The character is reported…",
  "external_url": "https://github.com/android/ndk/issues/2260",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/hnrss-org/2026/10/2026-10-14-aosp-source-releases-are-so-late-developers-disassemble.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/hnrss-org/2026/10/2026-10-14-aosp-source-releases-are-so-late-developers-disassemble.md"
  },
  "id": "urn:aggr:item:207a323e2867a5a43e9d447c72945d8fac8de5f4",
  "isBasedOn": {
    "@id": "https://github.com/android/ndk/issues/2260"
  },
  "mainEntity": {
    "@id": "https://github.com/android/ndk/issues/2260",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-14-aosp-source-releases-are-so-late-developers-disassemble/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "tharen"
      }
    ],
    "dateModified": "2026-10-14T06:18:01+00:00",
    "datePublished": "2026-10-14T06:18:01+00:00",
    "headline": "AOSP Source Releases Are So Late Developers Disassemble Libraries to Report Bugs",
    "timeRequired": "PT4M",
    "url": "https://github.com/android/ndk/issues/2260",
    "wordCount": 760
  },
  "name": "Archived snapshot: AOSP Source Releases Are So Late Developers Disassemble Libraries to Report Bugs",
  "preview": null,
  "reading_minutes": 4,
  "replicated_at": null,
  "source": "hnrss-org",
  "source_name": "Hacker News: Front Page",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-14T13:03:36.501235501+00:00",
  "title": "AOSP Source Releases Are So Late Developers Disassemble Libraries to Report Bugs",
  "url": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-10-14-aosp-source-releases-are-so-late-developers-disassemble/",
  "word_count": 760
}