schemagen: arrays of booleans map to std::vector<bool> whose back() is a proxy rvalue — generated code does not compile #64

Open
opened 2026-08-30 22:06:43 +00:00 by weaselbot · 0 comments
Member

Summary

For any schema that contains an array of non-nullable boolean, schemagen emits a std::vector<bool> and code of the form

return &v->back();

inside RootBuilder::engage. std::vector<bool> is the bit-packed specialization: back() returns a prvalue proxy (std::vector<bool>::reference, i.e. _Bit_reference), so taking its address is ill-formed. The generated header fails to compile on both GCC and Clang, and schemagen itself exits 0 (no GenError), so the breakage is only discovered later at C++ compile time.

Root cause

contrib/schemagen/weaseljson_schemagen.py:

  • Emitter.base_cpp (lines 541-554): a boolean element type maps to "bool" (line 546), so an array of bools produces std::vector<bool> at line 553 (storage_cpp(..., False) -> std::vector<bool>).
  • Emitter._engage (lines 772-783): for every registered array kind with a non-nullable element it emits return &v->back(); (line 782). This is only valid if back() returns a real reference — which it does not for std::vector<bool>.

Reproduction

schema.json:

{
  "type": "array",
  "items": { "type": "boolean" }
}

Commands:

python3 contrib/schemagen/weaseljson_schemagen.py schema.json -o out.h     # exits 0
g++ -std=c++20 -fsyntax-only -I include -I . any.cpp   # any TU including out.h

Generated fragment (out.h):

case Kind::Arr0: {
  auto *v = (std::vector<bool> *)f.dest;
  if (fresh) v->emplace_back();
  return &v->back();   // line ~392
}

GCC 16:

error: taking address of rvalue [-fpermissive]
  392 |       return &v->back();
      |               ~~~~~~~^~
warning: returning pointer to temporary [-Wreturn-local-addr]

Clang 21:

error: taking the address of a temporary object of type 'reference' (aka '_Bit_reference') [-Waddress-of-temporary]

Scope (all verified):

  • root-level array of bools: fails (this repro)
  • array of bools as an object field: {"properties": {"flags": {"type": "array", "items": {"type": "boolean"}}}} — fails
  • nested arrays {"type": "array", "items": {"type": "array", "items": {"type": "boolean"}}} — fails
  • nullable root variant {"type": ["array", "null"], "items": {"type": "boolean"}} — fails (same std::vector<bool> engage path)
  • {"type": "boolean"} as a plain member or root scalar — compiles (uses &o->x / &result_)
  • nullable items {"type": ["boolean", "null"]} (std::vector<std::optional<bool>>) — compiles, because engage uses auto &e = v->back(); ... return &*e; there

So the bug hits exactly the non-nullable-bool-items case: the very common flags: [true, false] schema.

Impact

Any valid JSON Schema containing an array of booleans produces a generated header that does not compile, with schemagen reporting success (exit code 0). README's contract says generation rejects anything that cannot map onto C++ structs ("Only the subset of JSON Schema that maps naturally onto C++ structs is supported. Anything else is rejected at generation time"); here an unmappable case slips through to compile time.

Note that even if it were forced to compile with -fpermissive, the semantics would still be broken: cbBool does *(bool *)engage(f, true) = value;, which would write through a pointer to the destroyed temporary proxy — so the fix likely needs to reject std::vector<bool> element types at generation time (matching the existing "no natural C++ struct mapping" policy) or change the element storage (e.g. std::vector<char>/std::deque<bool>), not merely change the cast. The existing in-repo tests don't cover this (example.schema.json contains no bool arrays; test_gen.cpp / nullable schemas use int/string arrays).

## Summary For any schema that contains an **array of non-nullable `boolean`**, schemagen emits a `std::vector<bool>` and code of the form ```cpp return &v->back(); ``` inside `RootBuilder::engage`. `std::vector<bool>` is the bit-packed specialization: `back()` returns a *prvalue proxy* (`std::vector<bool>::reference`, i.e. `_Bit_reference`), so taking its address is ill-formed. The generated header fails to compile on both GCC and Clang, and `schemagen` itself exits 0 (no `GenError`), so the breakage is only discovered later at C++ compile time. ## Root cause `contrib/schemagen/weaseljson_schemagen.py`: - `Emitter.base_cpp` (lines 541-554): a boolean element type maps to `"bool"` (line 546), so an array of bools produces `std::vector<bool>` at line 553 (`storage_cpp(..., False)` -> `std::vector<bool>`). - `Emitter._engage` (lines 772-783): for every registered array kind with a non-nullable element it emits `return &v->back();` (line 782). This is only valid if `back()` returns a real reference — which it does not for `std::vector<bool>`. ## Reproduction `schema.json`: ```json { "type": "array", "items": { "type": "boolean" } } ``` Commands: ```sh python3 contrib/schemagen/weaseljson_schemagen.py schema.json -o out.h # exits 0 g++ -std=c++20 -fsyntax-only -I include -I . any.cpp # any TU including out.h ``` Generated fragment (`out.h`): ```cpp case Kind::Arr0: { auto *v = (std::vector<bool> *)f.dest; if (fresh) v->emplace_back(); return &v->back(); // line ~392 } ``` GCC 16: ``` error: taking address of rvalue [-fpermissive] 392 | return &v->back(); | ~~~~~~~^~ warning: returning pointer to temporary [-Wreturn-local-addr] ``` Clang 21: ``` error: taking the address of a temporary object of type 'reference' (aka '_Bit_reference') [-Waddress-of-temporary] ``` Scope (all verified): - root-level array of bools: fails (this repro) - array of bools as an object field: `{"properties": {"flags": {"type": "array", "items": {"type": "boolean"}}}}` — fails - nested arrays `{"type": "array", "items": {"type": "array", "items": {"type": "boolean"}}}` — fails - nullable root variant `{"type": ["array", "null"], "items": {"type": "boolean"}}` — fails (same `std::vector<bool>` engage path) - `{"type": "boolean"}` as a plain member or root scalar — compiles (uses `&o->x` / `&result_`) - **nullable** items `{"type": ["boolean", "null"]}` (`std::vector<std::optional<bool>>`) — compiles, because `engage` uses `auto &e = v->back(); ... return &*e;` there So the bug hits exactly the non-nullable-bool-items case: the very common `flags: [true, false]` schema. ## Impact Any valid JSON Schema containing an array of booleans produces a generated header that does not compile, with schemagen reporting success (exit code 0). README's contract says generation rejects anything that cannot map onto C++ structs ("Only the subset of JSON Schema that maps naturally onto C++ structs is supported. Anything else is rejected at generation time"); here an unmappable case slips through to compile time. Note that even if it were forced to compile with `-fpermissive`, the semantics would still be broken: `cbBool` does `*(bool *)engage(f, true) = value;`, which would write through a pointer to the destroyed temporary proxy — so the fix likely needs to reject `std::vector<bool>` element types at generation time (matching the existing "no natural C++ struct mapping" policy) or change the element storage (e.g. `std::vector<char>`/`std::deque<bool>`), not merely change the cast. The existing in-repo tests don't cover this (example.schema.json contains no bool arrays; test_gen.cpp / nullable schemas use int/string arrays).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#64