schemagen breaks required/duplicate checks for objects with >32 properties #3

Closed
opened 2026-06-17 23:38:24 +00:00 by weaselbot · 0 comments
Member

contrib/schemagen/weaseljson_schemagen.py tracks object field presence with a 32-bit bitmask. The generated builder uses uint32_t seen (template line 797) and shifts such as 1u << p.field (line 832) and 1u << idx (line 884). The required mask is built by _bitmask (line 665) and emitted by _reqmask (line 672).

For schemas with more than 32 properties this breaks in two ways:

  1. The generated requiredMask literal overflows uint32_t. For 40 required fields it emits 0xffffffffffu, which is truncated to 0xffffffffu at compile time (GCC warns: "conversion from ... changes value").
  2. Shifts by field indices >= 32 are undefined behavior in C++. On typical compilers the shift wraps modulo 32, so fields p32..p39 alias bits 0..7. This makes p32 appear as a duplicate of p0, and a document that is genuinely missing p32 (or any index >=32) can be accepted because the truncated mask no longer contains that bit.

Reproducer: generate from a schema with 40 required string properties and feed a JSON object that supplies all of them except p32. The builder accepts it even though p32 is required. Feeding a document with both p0 and p32 causes a false duplicate-key rejection.

The generator should either reject schemas with more than 32 properties or, preferably, use a bitset / uint64_t / vector-backed tracker sized to the actual field count.

`contrib/schemagen/weaseljson_schemagen.py` tracks object field presence with a 32-bit bitmask. The generated builder uses `uint32_t seen` (template line 797) and shifts such as `1u << p.field` (line 832) and `1u << idx` (line 884). The required mask is built by `_bitmask` (line 665) and emitted by `_reqmask` (line 672). For schemas with more than 32 properties this breaks in two ways: 1. The generated `requiredMask` literal overflows `uint32_t`. For 40 required fields it emits `0xffffffffffu`, which is truncated to `0xffffffffu` at compile time (GCC warns: "conversion from ... changes value"). 2. Shifts by field indices `>= 32` are undefined behavior in C++. On typical compilers the shift wraps modulo 32, so fields `p32`..`p39` alias bits `0`..`7`. This makes `p32` appear as a duplicate of `p0`, and a document that is genuinely missing `p32` (or any index >=32) can be accepted because the truncated mask no longer contains that bit. Reproducer: generate from a schema with 40 required string properties and feed a JSON object that supplies all of them except `p32`. The builder accepts it even though `p32` is required. Feeding a document with both `p0` and `p32` causes a false duplicate-key rejection. The generator should either reject schemas with more than 32 properties or, preferably, use a bitset / `uint64_t` / vector-backed tracker sized to the actual field count.
weaselbot was assigned by andrew 2026-06-18 14:05:34 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#3