schemagen: required entries with no matching properties key are silently dropped - generated parser accepts schema-invalid documents #63

Closed
opened 2026-08-30 15:48:02 +00:00 by weaselbot · 0 comments
Member

Summary

weaseljson_schemagen.py silently drops any required entry that does not have a matching key in properties. Because the only supported object mode is additionalProperties: false (which rejects all keys not listed in properties), such a schema is unsatisfiable, yet schemagen accepts it at generation time and the generated parser happily accepts documents that the schema invalidates.

This contradicts the README contract:

  • contrib/schemagen/README.md lines 50-52 ("Schema violations (rejected at parse time)"): "missing required property (top-level or nested)"
  • contrib/schemagen/README.md header of the parser: "WeaselJson_OK means the document is both valid JSON and schema-valid"

and silently produces a permissive parser where a JSON Schema validator would reject every document.

Where

contrib/schemagen/weaseljson_schemagen.py:

  • Line 433: required = set(node.get("required", []))
  • Lines 435-443: required is consulted only for keys appearing in properties (obj.fields.append(Field(key, cpp, ty, key in required, nullable))); entries of required with no matching property simply vanish.
  • requiredMask() (lines ~836-845) emits mask words/bits only for obj.fields, so there is never a bit for a key that is not a field.

The check in generated cbEndObject() (if ((f.seen[i] & req[i]) != req[i]) missing = true) therefore never fires for such keys.

Reproduction

schema.json:

{
  "type": "object",
  "additionalProperties": false,
  "properties": { "a": { "type": "integer" } },
  "required": ["b"]
}

This schema is valid input for the generator (nothing in the "not supported" list), but under JSON Schema semantics no document can satisfy it: "b" is required, yet whenever "b" appears it violates additionalProperties: false (it has no property entry).

Generate and accept:

python3 contrib/schemagen/weaseljson_schemagen.py schema.json -o gen.h

Generation succeeds with no error, emitting requiredMask { 0x0u } for Kind::Root1 even though key "b" is required.

RootBuilder b;
char buf[] = "{\"a\": 1}";   // missing required key "b"
WeaselJsonStatus s = b.feed(buf, sizeof(buf) - 1);
if (s == WeaselJson_AGAIN) s = b.finish();
// s == WeaselJson_OK   <-- document is accepted

Observed:

missing required key 'b' -> status 0 (ACCEPTED - should be REJECT)
accepted result: a=1

Conversely, {"a": 1, "b": 2} is rejected (unknown key), which is the only part that happens to be correct; everything else is accepted.

The same gap applies to any nested object schema, not just the root.

Expected behavior

Following the established pattern from issue #55 / PR #58 (absent additionalProperties is now rejected at generation time), the generator should reject schemas where set(required) is not a subset of properties.keys() at generation time, e.g. GenError: "required key 'b' has no matching properties entry". There is no correct parse-time fallback: since additionalProperties: false is mandatory and is the only supported mode, the schema can never match any document, so a generation-time GenError (like the other unsupported constructs) is the appropriate behavior.

Impact

A schema with a typo/dead required entry (e.g. key renamed in properties but not in required, or a required key simply forgotten during properties refactoring) silently produces a parser that accepts documents missing that required key, i.e. invalid data passes validation instead of being rejected at generation or parse time.

## Summary `weaseljson_schemagen.py` silently drops any `required` entry that does not have a matching key in `properties`. Because the only supported object mode is `additionalProperties: false` (which rejects all keys not listed in `properties`), such a schema is unsatisfiable, yet schemagen accepts it at generation time and the generated parser happily accepts documents that the schema invalidates. This contradicts the README contract: - `contrib/schemagen/README.md` lines 50-52 ("Schema violations (rejected at parse time)"): *"missing required property (top-level or nested)"* - `contrib/schemagen/README.md` header of the parser: *"`WeaselJson_OK` means the document is both valid JSON and schema-valid"* and silently produces a permissive parser where a JSON Schema validator would reject **every** document. ## Where `contrib/schemagen/weaseljson_schemagen.py`: - Line 433: `required = set(node.get("required", []))` - Lines 435-443: `required` is consulted only for keys appearing in `properties` (`obj.fields.append(Field(key, cpp, ty, key in required, nullable))`); entries of `required` with no matching property simply vanish. - `requiredMask()` (lines ~836-845) emits mask words/bits only for `obj.fields`, so there is never a bit for a key that is not a field. The check in generated `cbEndObject()` (`if ((f.seen[i] & req[i]) != req[i]) missing = true`) therefore never fires for such keys. ## Reproduction `schema.json`: ```json { "type": "object", "additionalProperties": false, "properties": { "a": { "type": "integer" } }, "required": ["b"] } ``` This schema is valid input for the generator (nothing in the "not supported" list), but under JSON Schema semantics no document can satisfy it: `"b"` is required, yet whenever `"b"` appears it violates `additionalProperties: false` (it has no property entry). Generate and accept: ```sh python3 contrib/schemagen/weaseljson_schemagen.py schema.json -o gen.h ``` Generation succeeds with no error, emitting `requiredMask` `{ 0x0u }` for `Kind::Root1` even though key `"b"` is required. ```cpp RootBuilder b; char buf[] = "{\"a\": 1}"; // missing required key "b" WeaselJsonStatus s = b.feed(buf, sizeof(buf) - 1); if (s == WeaselJson_AGAIN) s = b.finish(); // s == WeaselJson_OK <-- document is accepted ``` Observed: ``` missing required key 'b' -> status 0 (ACCEPTED - should be REJECT) accepted result: a=1 ``` Conversely, `{"a": 1, "b": 2}` is rejected (unknown key), which is the only part that happens to be correct; everything else is accepted. The same gap applies to any nested object schema, not just the root. ## Expected behavior Following the established pattern from issue #55 / PR #58 (absent `additionalProperties` is now rejected at generation time), the generator should reject schemas where `set(required)` is not a subset of `properties.keys()` at generation time, e.g. `GenError: "required key 'b' has no matching properties entry"`. There is no correct parse-time fallback: since `additionalProperties: false` is mandatory and is the only supported mode, the schema can never match any document, so a generation-time `GenError` (like the other unsupported constructs) is the appropriate behavior. ## Impact A schema with a typo/dead `required` entry (e.g. key renamed in `properties` but not in `required`, or a required key simply forgotten during `properties` refactoring) silently produces a parser that accepts documents missing that required key, i.e. invalid data passes validation instead of being rejected at generation or parse time.
weaselbot was assigned by andrew 2026-08-30 21:25:58 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: weaselab/weaseljson#63