Skip to main content

Module dictionary

Module dictionary 

Source
Expand description

Dictionary — string interning (§85 Phase 1).

Implements xmlDict, the libxml2 string interning mechanism for efficient string comparison and memory sharing.

§UPSTREAM-PARITY

The libxml2 xmlDict is a hash-table-backed string interning dictionary. Key properties:

  • Strings are interned (stored once, reused by reference)
  • Interned strings are reference-counted
  • Sub-dictionaries share the parent string table but have their own reference counting
  • Dictionary limits prevent denial-of-service via excessive unique strings
  • xmlDictSetLimit controls the maximum number of strings
  • xmlDictGetUsage returns the current number of strings

§Thread safety

xmlDict is NOT thread-safe for concurrent modification. However, concurrent reads are safe once the dictionary is populated. This matches upstream behavior.

§Phase 1 status

Complete — all dictionary functions are implemented. Uses hashbrown::HashTable for the underlying hash table.

§Upstream contract

Mirrors upstream dict.c / dict.h (SRC-LIBXML2-2.15.0-DICT-C, parity target libxml2 2.15.3 oracle): xmlDictCreate / xmlDictCreateSub / xmlDictLookup / xmlDictQLookup / xmlDictSetLimit / xmlDictGetUsage / xmlDictReference / xmlDictFree, with the FNV-1a key hashing of xmlDictComputeFastQKey.

§Ownership & safety invariants

Dict-owned strings are stable for the dict lifetime and invalidated by xmlDictFree — the dict must outlive every string interned from it and the docs that reference them (OWNERSHIP_ATLAS §3, §7.4). A sub-dict shares the parent table but keeps its own refcounts; xmlDictReference bumps the owning dict so parser/doc sharing stays safe. Concurrent reads are safe once populated; concurrent modification is NOT (matches upstream).

§Historical quirks & epochs

The default dictionary-size limit dates from the 2.9.0 hardening epoch (QUIRK-0001 / SEC-0001, commit 52d8ade7 2012-07-30): excessive unique strings fail unless XML_PARSE_HUGE lifts the limits. CVE-2015-7497 (commit 6360a31a, “Avoid an heap buffer overflow in xmlDictComputeFastQKey”) fixed the key-hash path the FNV hasher here reproduces (SEC-0008).

§Deliberate oddities

The FNV-1a hasher is deliberately NOT a general-purpose Rust hasher: it reproduces the upstream hash so interning behavior (and the xmlDictQLookup collision handling) matches the oracle.

§Proving courts

PARSER-LIMIT-* courts verify the default limits and the XML_PARSE_HUGE relaxation; DICT-* courts cover lookups and key hashing; the SECURITY-LIMITS court covers entity/amplification paths that consult the dict. All differential courts require byte-identical output vs the oracle DSO; cargo test runs the Rust unit suites.

§Tempting simplifications that would break parity

Do not replace the interning table with a plain Rust HashMap of Strings: callers observe stable xmlDictLookup pointers, refcount semantics, sub-dict sharing and xmlDictSetLimit accounting — all part of the C ABI. Do not change the hash: xmlDictQLookup collision order is observable, and the CVE-2015-7497 fix constrains the implementation.

Structs§

Dict
The dictionary struct (opaque in the C ABI).

Functions§

dict_create
Create a new dictionary.
dict_create_sub
Create a sub-dictionary that shares strings with its parent.
dict_exists
Check if a string exists in the dictionary without adding it.
dict_free
Free a dictionary and all its interned strings.
dict_get_usage
Get the current number of entries in the dictionary.
dict_lookup
Look up a string in the dictionary, adding it if not found.
dict_owns
Whether s is one of this dictionary’s interned strings (walking the parent chain for sub-dictionaries).
dict_set_limit
Set the maximum number of entries allowed in the dictionary.
dict_size
Get the number of entries in the dictionary.