Skip to content

Swift grammar scanner shifts past the width of its type in two places — one is undefined on Windows #1978

Description

@CaptainMittens

Version

Built from source at ae5de7fe (main), release line v0.10.8

Platform

Windows (x64)

Install channel

Built from source

Binary variant

standard

What happened, and what did you expect?

The vendored Swift grammar scanner shifts a literal 1 past the width of its own type in
two places. Both are undefined behaviour.

Line Code Left operand Shift count State
514 1 << suppressor int, 32 bits everywhere reaches 32 fixed in #1977
131 1UL << FAKE_TRY_BANG unsigned long, 32 bits on Windows exactly 32 open

Both live in internal/cbm/vendored/grammars/swift/scanner.c.

For line 514, TOKEN_COUNT is 33, so the loop counter reaches 32. The mask being tested
is a uint64_t, which makes the intent unambiguous.

For line 131, FAKE_TRY_BANG is 32 — I counted the TokenType enum in that file: 33
entries, and FAKE_TRY_BANG is the last. On Linux and macOS unsigned long is 64 bits
and the shift is fine. On Windows it is 32 bits, so the shift count equals the full width
of the type.

I expected the entry to produce 1 << 32 as a 64-bit value. On Windows it produces
whatever the compiler decides an undefined shift produces.

Neither of these is only my reading of the standard. Upstream
alex-pinkus/tree-sitter-swift fixed both independently:

  • line 514 in fb63a7004f07 (2026-04-06), "Fix UB in eat_operators"
  • line 131 in 6ab8d1d74ebd (2026-08-10), "Fix undefined 32-bit shift in OP_SYMBOL_SUPPRESSOR on LLP64 targets"

Both changes are one token each: 1 to 1ULL, and 1UL to 1ULL.

MANIFEST.md pins the Swift grammar at 8abb3e8b3325, dated 2026-03-20. Both upstream
fixes landed after that pin. Our copy is a faithful vendor rather than a local edit — I
checked it byte-identical to upstream at that commit.

Why nothing here catches it. This is the part I think is worth your time, because it
explains why Windows CI has stayed green:

  1. Line 131 is a compile-time constant expression, so no sanitizer sees it at run time.
  2. Makefile.cbm:719 compiles every vendored grammar with -w, which switches off all
    warnings — including -Wshift-count-overflow, the one diagnostic that names this exact
    defect. The comment on line 718 gives the reason: "upstream code has warnings."
  3. CLANG64 on windows-latest is in CORE_WIN (.github/workflows/_test.yml:63), so
    the affected platform is built on every pull request and reports nothing.

A suggestion, not part of this report. Turning on just -Wshift-count-overflow for
vendored grammars — one narrow warning, not -Wall -Werror — would catch this class
across all 104 vendored scanners without reintroducing the upstream warning noise that
-w exists to suppress. I have not tried it and I am not proposing it here. Happy to
open a separate issue if you want it explored.

Reproduction

I have no Windows machine, so I have no runtime reproduction on the affected platform,
and I would rather say that than invent one.
Two honest substitutes.

1. The mechanism, which you can run anywhere. unsigned int is 32 bits on every
target, so it stands in for Windows' unsigned long:

#include <stdint.h>
#include <stdio.h>
int main(void) {
    uint64_t m = 1u << 32;   /* same shape as scanner.c:131 on Windows */
    printf("%llu\n", (unsigned long long)m);
}

With warnings on, the compiler names the defect:

repro.c:4:21: warning: shift count >= width of type [-Wshift-count-overflow]
    4 |     uint64_t m = 1u << 32;
      |                     ^  ~~

Compiled with -w, as the grammar build does, that warning disappears and the program
runs. The result is not merely wrong — it is unstable. Three runs of one binary on Apple
clang 21.0.0, arm64:

4338156640
4364321888
4377183328

At -O2 it settles on a different value again (8447164672). m is never actually
assigned; the compiler treats the shift as poison and leaves stack garbage behind. Writing
the same line correctly as (uint64_t)1 << 32 prints 4294967296 every time.

That is the mechanism, on my platform, with unsigned int standing in. It is not a
measurement of what Windows does with 1UL << 32
— I have not run that, and I am not
claiming the two produce the same value.

2. How to confirm the real effect, if you have a Windows box. Index a Swift file
containing a force-unwrap or try! on Windows and on Linux, then compare the extracted
tokens. OP_SYMBOL_SUPPRESSOR[BANG] is what stops try! emitting its ! as a token of
its own. If that entry is wrong on Windows, Swift extraction differs there and nothing
currently fails to say so.

What I am claiming, and what I am not. Established: the expression is undefined, its
location, and that upstream judged both lines worth fixing. Not established: that a
symptom is visible in extracted output today. I did not want to overstate a bug I found by
reading rather than by running.

Logs

Not applicable — this is a source-level defect found by inspection, not a runtime failure.
The compiler diagnostic that would normally appear is suppressed by -w on grammar builds.

Diagnostics trajectory (memory / performance / leak issues)

Not applicable — not a memory or performance issue.

Project scale (if relevant)

Not relevant — the defect is in a vendored grammar, independent of project size.

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edgeswindowsWindows-specific issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions