makiri 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/conformance.yml +22 -0
  3. data/.github/workflows/libfuzzer.yml +83 -0
  4. data/.github/workflows/security.yml +88 -3
  5. data/.github/workflows/valgrind.yml +135 -0
  6. data/CHANGELOG.md +60 -2
  7. data/README.md +81 -77
  8. data/Rakefile +194 -3
  9. data/ext/makiri/bridge/ruby_string.c +119 -66
  10. data/ext/makiri/core/mkr_alloc.c +40 -3
  11. data/ext/makiri/core/mkr_alloc.h +27 -4
  12. data/ext/makiri/core/mkr_buf.c +13 -3
  13. data/ext/makiri/core/mkr_buf.h +80 -5
  14. data/ext/makiri/core/mkr_core.c +143 -0
  15. data/ext/makiri/core/mkr_core.h +10 -1
  16. data/ext/makiri/core/mkr_span.h +186 -0
  17. data/ext/makiri/core/mkr_utf8.c +101 -0
  18. data/ext/makiri/core/mkr_utf8.h +88 -0
  19. data/ext/makiri/extconf.rb +104 -9
  20. data/ext/makiri/fuzz/Makefile +95 -0
  21. data/ext/makiri/fuzz/check_fuzzer.cc +4 -0
  22. data/ext/makiri/fuzz/xml_fuzz.c +24 -0
  23. data/ext/makiri/fuzz/xpath_fuzz.c +109 -0
  24. data/ext/makiri/glue/glue.h +8 -0
  25. data/ext/makiri/glue/ruby_doc.c +20 -24
  26. data/ext/makiri/glue/ruby_html_css.c +58 -12
  27. data/ext/makiri/glue/ruby_html_mutate.c +11 -6
  28. data/ext/makiri/glue/ruby_html_node.c +3 -32
  29. data/ext/makiri/glue/ruby_node.c +39 -0
  30. data/ext/makiri/glue/ruby_xml.c +198 -16
  31. data/ext/makiri/glue/ruby_xml_node.c +46 -59
  32. data/ext/makiri/glue/ruby_xpath.c +4 -4
  33. data/ext/makiri/lexbor_compat/source_loc.c +14 -16
  34. data/ext/makiri/lexbor_compat/utf8_input.c +5 -78
  35. data/ext/makiri/makiri.c +45 -0
  36. data/ext/makiri/xml/mkr_xml.h +2 -3
  37. data/ext/makiri/xml/mkr_xml_chars.c +67 -97
  38. data/ext/makiri/xml/mkr_xml_index.c +169 -0
  39. data/ext/makiri/xml/mkr_xml_index.h +48 -0
  40. data/ext/makiri/xml/mkr_xml_mutate.c +63 -121
  41. data/ext/makiri/xml/mkr_xml_node.c +147 -15
  42. data/ext/makiri/xml/mkr_xml_node.h +71 -6
  43. data/ext/makiri/xml/mkr_xml_tree.c +185 -149
  44. data/ext/makiri/xpath/mkr_css.c +1023 -0
  45. data/ext/makiri/xpath/mkr_css.h +65 -0
  46. data/ext/makiri/xpath/mkr_xpath.c +37 -0
  47. data/ext/makiri/xpath/mkr_xpath.h +13 -0
  48. data/ext/makiri/xpath/mkr_xpath_eval_body.h +373 -90
  49. data/ext/makiri/xpath/mkr_xpath_funcs_body.h +249 -231
  50. data/ext/makiri/xpath/mkr_xpath_internal.h +89 -9
  51. data/ext/makiri/xpath/mkr_xpath_lex.c +94 -124
  52. data/ext/makiri/xpath/mkr_xpath_node_access_xml.h +6 -3
  53. data/ext/makiri/xpath/mkr_xpath_number.c +109 -0
  54. data/ext/makiri/xpath/mkr_xpath_parse.c +79 -90
  55. data/ext/makiri/xpath/mkr_xpath_shared.c +40 -24
  56. data/ext/makiri/xpath/mkr_xpath_value_body.h +50 -24
  57. data/lib/makiri/cdata_section.rb +1 -3
  58. data/lib/makiri/comment.rb +1 -3
  59. data/lib/makiri/document.rb +8 -0
  60. data/lib/makiri/element.rb +1 -3
  61. data/lib/makiri/processing_instruction.rb +1 -3
  62. data/lib/makiri/text.rb +1 -3
  63. data/lib/makiri/version.rb +1 -1
  64. data/lib/makiri/xml/builder.rb +263 -0
  65. data/lib/makiri/xml/node_methods.rb +47 -0
  66. data/lib/makiri.rb +1 -0
  67. data/script/check_alloc_failures.rb +266 -0
  68. data/script/check_c_safety.rb +45 -2
  69. data/script/check_c_safety_allowlist.yml +19 -0
  70. data/script/check_leaks.rb +64 -0
  71. data/script/leaks_harness.rb +64 -0
  72. data/vendor/lexbor/CMakeLists.txt +6 -0
  73. data/vendor/lexbor/README.md +12 -0
  74. data/vendor/lexbor/config.cmake +1 -1
  75. data/vendor/lexbor/source/lexbor/core/base.h +1 -1
  76. data/vendor/lexbor/source/lexbor/core/config.cmake +9 -1
  77. data/vendor/lexbor/source/lexbor/css/selectors/pseudo_state.c +2 -3
  78. data/vendor/lexbor/source/lexbor/css/selectors/state.c +3 -0
  79. data/vendor/lexbor/source/lexbor/dom/interfaces/element.c +21 -0
  80. data/vendor/lexbor/source/lexbor/dom/interfaces/element.h +5 -0
  81. data/vendor/lexbor/source/lexbor/encoding/decode.c +33 -4
  82. data/vendor/lexbor/source/lexbor/html/base.h +1 -1
  83. data/vendor/lexbor/source/lexbor/html/interfaces/select_element.c +4 -0
  84. data/vendor/lexbor/source/lexbor/html/serialize.c +545 -41
  85. data/vendor/lexbor/source/lexbor/html/serialize.h +2 -1
  86. data/vendor/lexbor/source/lexbor/html/tokenizer.h +2 -2
  87. data/vendor/lexbor/source/lexbor/html/tree/insertion_mode/in_body.c +1 -1
  88. data/vendor/lexbor/source/lexbor/html/tree.c +6 -6
  89. data/vendor/lexbor/source/lexbor/selectors/selectors.c +12 -3
  90. data/vendor/lexbor/source/lexbor/url/base.h +1 -1
  91. data/vendor/lexbor/source/lexbor/url/url.c +5 -2
  92. data/vendor/lexbor/source/lexbor/url/url.h +9 -0
  93. data/vendor/lexbor/version +1 -1
  94. metadata +19 -1
@@ -13,6 +13,7 @@
13
13
 
14
14
  #include <stdint.h>
15
15
  #include <stddef.h>
16
+ #include "../core/mkr_buf.h" /* mkr_spanbuf_t (the arena hand-fill writer) */
16
17
 
17
18
  /* The XML representation's DOM node-type constants - the counterpart of Lexbor's
18
19
  * LXB_DOM_NODE_TYPE_* for HTML. The numeric values mirror the DOM/Lexbor
@@ -50,7 +51,9 @@ typedef enum {
50
51
  * lengths stay uint32 (the deliberate per-slice 4 GiB cap; see the enum note).
51
52
  * Names/values are arena-owned byte slices (case-preserved). */
52
53
  typedef struct mkr_xml_node {
53
- uint8_t type; /* mkr_xml_node_type_t */
54
+ mkr_xml_node_type_t type; /* free: the next field is an 8-aligned pointer,
55
+ * so this sits in padding either way (node stays
56
+ * 128 B) - keep the real enum type for safety */
54
57
  struct mkr_xml_node *parent, *first_child, *last_child, *prev, *next;
55
58
  struct mkr_xml_node *attrs; /* element: head of attribute list (type=ATTRIBUTE) */
56
59
  const char *qname; /* element/attr raw "prefix:local" (contiguous; local/prefix slice into it) */
@@ -62,6 +65,17 @@ typedef struct mkr_xml_node {
62
65
  uint32_t line, col; /* source position (element only; 0 = none) */
63
66
  } mkr_xml_node_t;
64
67
 
68
+ /* A QName decomposed into its contiguous "prefix:local" parts (the same layout a
69
+ * built node carries): +qname+ is the whole name, +prefix+/+local+ slice into it
70
+ * (prefix points at +qname+; prefix_len 0 = unprefixed). The single value passed
71
+ * through the split/resolve/assign primitives below, so those never take the six
72
+ * fields as loose arguments. */
73
+ typedef struct {
74
+ const char *qname; uint32_t qname_len;
75
+ const char *prefix; uint32_t prefix_len; /* prefix_len 0 = unprefixed */
76
+ const char *local; uint32_t local_len;
77
+ } mkr_xml_qname_t;
78
+
65
79
  typedef struct mkr_xml_arena_chunk mkr_xml_arena_chunk_t;
66
80
 
67
81
  /* A document: its append-only arena, the root element (NULL until built), and
@@ -85,6 +99,14 @@ typedef struct mkr_xml_doc {
85
99
  * node: local/qname = name, prefix = public
86
100
  * (external) id, value = system id; a 0-length
87
101
  * prefix/value means that id is absent. */
102
+ void *name_index; /* lazily-built element-name index (mkr_xml_index.c),
103
+ * or NULL; dropped on every structural mutation. */
104
+ int has_encoding_decl; /* the parsed source's <?xml?> declaration carried an
105
+ * `encoding` pseudo-attribute. The serializer emits
106
+ * `encoding="UTF-8"` only when this is set (or an
107
+ * explicit encoding: is requested), so a built or
108
+ * declaration-less document round-trips to a bare
109
+ * `<?xml version="1.0"?>`, like Nokogiri. */
88
110
  } mkr_xml_doc_t;
89
111
  /* The PER-ELEMENT attribute cap (MKR_XML_MAX_ATTRS) is enforced by the tree
90
112
  * builder; the arena counts every node - attributes included - toward max_nodes. */
@@ -97,12 +119,21 @@ size_t mkr_xml_doc_memsize(const mkr_xml_doc_t *doc);
97
119
  * The single overflow- and budget-checked alloc choke point is PRIVATE to
98
120
  * mkr_xml_node.c. Callers never get a void* to cast: nodes via *_node (zeroed,
99
121
  * type-validated), copied name/value bytes via *_bytes (the arena owns them).
100
- * mkr_xml_arena_scratch_bytes returns a raw, uninitialised char buffer of exactly
101
- * +len+ bytes for the one caller that must fill it itself (entity expansion). On
102
- * any failure doc->oom is set (sticky) and the wrapper returns NULL. */
103
- mkr_xml_node_t *mkr_xml_arena_node(mkr_xml_doc_t *doc, uint8_t type);
122
+ * A caller that must fill a buffer itself uses mkr_xml_arena_spanbuf (below),
123
+ * never a raw pointer. On any failure doc->oom is set (sticky) and the wrapper
124
+ * returns NULL. */
125
+ mkr_xml_node_t *mkr_xml_arena_node(mkr_xml_doc_t *doc, mkr_xml_node_type_t type);
104
126
  const char *mkr_xml_arena_bytes(mkr_xml_doc_t *doc, const char *src, uint32_t len);
105
- char *mkr_xml_arena_scratch_bytes(mkr_xml_doc_t *doc, size_t len);
127
+
128
+ /* Carve +cap+ arena bytes and return a bounded writer (core mkr_spanbuf) over
129
+ * them - the ONLY way to hand-fill arena bytes. The writer owns the cursor and
130
+ * the bounds check, so a write past the cut is impossible by construction (it is
131
+ * refused and latches ok=false, never overruns the next cut). Fill it with the
132
+ * core mkr_spanbuf_putc / _write, then mkr_spanbuf_finish; a NULL finish means
133
+ * the backing alloc failed (doc->oom set) OR a write was refused (doc->oom stays
134
+ * OK -> a broken invariant, i.e. our bug). On alloc failure the returned writer
135
+ * is already not-ok (buf == NULL), so every write is a safe no-op. */
136
+ mkr_spanbuf_t mkr_xml_arena_spanbuf(mkr_xml_doc_t *doc, size_t cap);
106
137
 
107
138
  /* If attribute +a+ is an xmlns declaration, set the declared namespace prefix
108
139
  * (NULL/plen 0 for the default xmlns) and its URI as arena slices, and return 1;
@@ -111,6 +142,40 @@ char *mkr_xml_arena_scratch_bytes(mkr_xml_doc_t *doc, size_t len);
111
142
  int mkr_xml_node_xmlns_decl(const mkr_xml_node_t *a, const char **prefix, uint32_t *plen,
112
143
  const char **uri, uint32_t *ulen);
113
144
 
145
+ /* If the raw name [name,len) is an xmlns declaration ("xmlns" or "xmlns:PREFIX"),
146
+ * set the out-params to the declared prefix ("" / 0 for the default xmlns) and
147
+ * return 1; else return 0. prefix/plen may be NULL when only the boolean is
148
+ * wanted. The single byte-level xmlns detector the parser, the namespace
149
+ * resolver, and mkr_xml_node_xmlns_decl all share (the node version just reads
150
+ * the value too). */
151
+ int mkr_xml_xmlns_prefix(const char *name, uint32_t len, const char **prefix, uint32_t *plen);
152
+
153
+ /* Pre-order (document-order) descendant iteration over +root+'s subtree without
154
+ * recursion: start at +root+, then `cur = mkr_xml_preorder_next(root, cur)` until
155
+ * NULL. Visits +root+ first. The one shared form of the iterative DFS the index
156
+ * build and the mutation namespace resolver both need (no scope/state threaded). */
157
+ mkr_xml_node_t *mkr_xml_preorder_next(const mkr_xml_node_t *root, mkr_xml_node_t *cur);
158
+
159
+ /* Validate [name,len) as a well-formed XML 1.0 QName (full NameStartChar/NameChar
160
+ * scan) and split it into +out+ (prefix_len 0 = unprefixed). 0 on success, -1 if
161
+ * malformed. For the mutation path, whose input is not pre-scanned; the tree
162
+ * builder keeps its own lighter split (the name is already scan_name'd). */
163
+ int mkr_xml_qname_split(const char *name, uint32_t len, mkr_xml_qname_t *out);
164
+
165
+ /* Copy +qn+'s name into +doc+'s arena as one contiguous slice and point
166
+ * node->qname/local/prefix into it (ns_uri is the caller's concern). 0 on
167
+ * success, -1 on arena OOM (node left untouched, so the caller stays fail-closed).
168
+ * The single arena-copy-and-assign both the tree builder and the mutators use. */
169
+ int mkr_xml_qname_assign(mkr_xml_doc_t *doc, mkr_xml_node_t *node, const mkr_xml_qname_t *qn);
170
+
171
+ /* The QName parts a built node already carries, as a mkr_xml_qname_t value. */
172
+ static inline mkr_xml_qname_t
173
+ mkr_xml_qname_of(const mkr_xml_node_t *n)
174
+ {
175
+ mkr_xml_qname_t q = { n->qname, n->qname_len, n->prefix, n->prefix_len, n->local, n->local_len };
176
+ return q;
177
+ }
178
+
114
179
  /* Self-test of the arena's secure-by-design properties (overflow, budgets,
115
180
  * alignment, zero-init, copy-on-store, whole-free). Returns 0 on success or the
116
181
  * 1-based index of the first failing check. Run from Makiri.__c_selftest. */