From a4ed5a9171ca2ca4491f2a5844ce00b91b8fd4e1 Mon Sep 17 00:00:00 2001 From: Weaselbot Date: Tue, 23 Jun 2026 12:27:05 -0400 Subject: [PATCH] schemagen: reject duplicate unknown keys in non-strict objects Track unknown keys in a per-object unordered_set so that permissive objects (additionalProperties absent/true) still reject duplicate keys, matching the README guarantee. - Add std::unordered_set to Frame. - Insert unknown keys in cbKeyData and reject duplicates before skipping. - Add a permissive "loose" subobject to example.schema.json. - Test single unknown key accepted and duplicate unknown/known keys rejected. --- contrib/schemagen/example.schema.json | 8 ++++++++ contrib/schemagen/test_gen.cpp | 14 ++++++++++++++ contrib/schemagen/weaseljson_schemagen.py | 8 +++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/contrib/schemagen/example.schema.json b/contrib/schemagen/example.schema.json index e1c0f6b..a0ff867 100644 --- a/contrib/schemagen/example.schema.json +++ b/contrib/schemagen/example.schema.json @@ -52,6 +52,14 @@ } } }, + "loose": { + "type": "object", + "properties": { + "known": { + "type": "string" + } + } + }, "friends": { "type": "array", "items": { diff --git a/contrib/schemagen/test_gen.cpp b/contrib/schemagen/test_gen.cpp index 577e983..c52d9bc 100644 --- a/contrib/schemagen/test_gen.cpp +++ b/contrib/schemagen/test_gen.cpp @@ -186,6 +186,20 @@ int main() { expectReject(R"([1,2,3])", "array where object expected (root)"); expectReject(R"({"name":"x","age":1,)", "truncated / invalid json"); + // ---- duplicate keys in permissive (additionalProperties allowed) object + // ---- + { + RootBuilder b; + WeaselJsonStatus s = + parseStrided(b, R"({"name":"x","age":1,"loose":{"unknown":1}})"); + CHECK(s == WeaselJson_OK); + printf("ok single unknown key in non-strict object\n"); + } + expectReject(R"({"name":"x","age":1,"loose":{"unknown":1,"unknown":2}})", + "duplicate unknown key in non-strict object"); + expectReject(R"({"name":"x","age":1,"loose":{"known":"a","known":"b"}})", + "duplicate known key in non-strict object"); + if (failures == 0) { printf("\nALL TESTS PASSED\n"); return 0; diff --git a/contrib/schemagen/weaseljson_schemagen.py b/contrib/schemagen/weaseljson_schemagen.py index 11b268b..3143cc3 100644 --- a/contrib/schemagen/weaseljson_schemagen.py +++ b/contrib/schemagen/weaseljson_schemagen.py @@ -531,6 +531,7 @@ class Emitter: #include #include #include +#include #include #include "weaseljson.h" @@ -885,6 +886,7 @@ private: void *dest; int field = -1; // object: selected field (-1 want key, -2 skip) std::vector seen; // populated field bitset (object frames) + std::unordered_set unknown; // unknown keys seen in non-strict objects }}; static constexpr int kWantKey = -1; static constexpr int kSkip = -2; @@ -968,12 +970,16 @@ private: scratch_.append(buf, len); if (!done) return; int idx = matchKey(f.kind, scratch_); - scratch_.clear(); if (idx < 0) {{ if (isStrict(f.kind)) {{ reject(); return; }} + if (!f.unknown.insert(scratch_).second) {{ + reject(); return; // duplicate unknown key + }} + scratch_.clear(); f.field = kSkip; return; }} + scratch_.clear(); if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{ reject(); return; // duplicate key }}