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<std::string> 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.
This commit is contained in:
2026-06-23 15:08:48 -04:00
parent e8830e27e9
commit ab95fefb09
3 changed files with 29 additions and 1 deletions
+7 -1
View File
@@ -566,6 +566,7 @@ class Emitter:
#include <optional>
#include <string>
#include <string_view>
#include <unordered_set>
#include <vector>
#include "weaseljson.h"
@@ -931,6 +932,7 @@ private:
void *dest;
int field = -1; // object: selected field (-1 want key, -2 skip)
std::vector<uint64_t> seen; // populated field bitset (object frames)
std::unordered_set<std::string> unknown; // unknown keys seen in non-strict objects
}};
static constexpr int kWantKey = -1;
static constexpr int kSkip = -2;
@@ -1014,12 +1016,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
}}