makiri 0.5.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.github/workflows/libfuzzer.yml +1 -1
- data/.github/workflows/verify.yml +37 -0
- data/CHANGELOG.md +19 -4
- data/README.md +16 -0
- data/Rakefile +13 -0
- data/ext/makiri/bridge/bridge.h +28 -4
- data/ext/makiri/bridge/ruby_string.c +25 -0
- data/ext/makiri/core/mkr_alloc.c +6 -0
- data/ext/makiri/core/mkr_alloc.h +13 -4
- data/ext/makiri/core/mkr_buf.h +1 -0
- data/ext/makiri/core/mkr_text.h +28 -9
- data/ext/makiri/core/mkr_utf8.h +8 -1
- data/ext/makiri/fuzz/Makefile +14 -6
- data/ext/makiri/fuzz/xml_xpath_fuzz.c +87 -0
- data/ext/makiri/glue/ruby_html_css.c +99 -10
- data/ext/makiri/glue/ruby_html_mutate.c +5 -5
- data/ext/makiri/xpath/mkr_xpath.c +12 -0
- data/ext/makiri/xpath/mkr_xpath_eval_body.h +18 -8
- data/ext/makiri/xpath/mkr_xpath_funcs_body.h +47 -38
- data/ext/makiri/xpath/mkr_xpath_xml_selftest.c +7 -10
- data/lib/makiri/version.rb +1 -1
- data/script/check_c_safety.rb +11 -0
- data/script/check_c_safety_allowlist.yml +9 -12
- data/suppressions/ruby.supp +22 -0
- data/verify/Makefile +237 -0
- data/verify/cbmc_models.c +38 -0
- data/verify/harness_alloc.c +257 -0
- data/verify/harness_buf.c +167 -0
- data/verify/harness_hash.c +28 -0
- data/verify/harness_span.c +213 -0
- data/verify/harness_spanbuf.c +98 -0
- data/verify/harness_utf8.c +70 -0
- data/verify/harness_utf8_chain.c +64 -0
- data/verify/harness_utf8_words.c +54 -0
- data/verify/harness_xml_chars.c +59 -0
- data/verify/harness_xml_expand.c +72 -0
- data/verify/harness_xml_parse.c +42 -0
- data/verify/harness_xpath_lex.c +51 -0
- data/verify/harness_xpath_number.c +68 -0
- data/verify/harness_xpath_parse.c +57 -0
- data/verify/selftest_main.c +16 -0
- data/verify/stub.c +10 -0
- data/verify/verify.h +54 -0
- metadata +22 -1
|
@@ -219,7 +219,7 @@ mkr_node_aset(VALUE self, VALUE rb_name, VALUE rb_value)
|
|
|
219
219
|
rb_raise(mkr_eError, "cannot set an attribute on a non-element node");
|
|
220
220
|
}
|
|
221
221
|
mkr_ruby_borrowed_text_t nv = mkr_ruby_verified_text(rb_name, "attribute name");
|
|
222
|
-
|
|
222
|
+
mkr_ruby_borrowed_data_t vv = mkr_ruby_verified_data(rb_value, "attribute value");
|
|
223
223
|
lxb_dom_attr_t *attr = lxb_dom_element_set_attribute(
|
|
224
224
|
lxb_dom_interface_element(node),
|
|
225
225
|
(const lxb_char_t *)nv.ptr, nv.len,
|
|
@@ -289,7 +289,7 @@ mkr_node_set_attribute_ns(VALUE self, VALUE rb_ns, VALUE rb_qname, VALUE rb_valu
|
|
|
289
289
|
lxb_dom_element_t *el = lxb_dom_interface_element(node);
|
|
290
290
|
|
|
291
291
|
mkr_ruby_borrowed_text_t qv = mkr_ruby_verified_text(rb_qname, "attribute qualified name");
|
|
292
|
-
|
|
292
|
+
mkr_ruby_borrowed_data_t vv = mkr_ruby_verified_data(rb_value, "attribute value");
|
|
293
293
|
|
|
294
294
|
mkr_ruby_borrowed_text_t nv = {0};
|
|
295
295
|
bool have_ns = false;
|
|
@@ -442,7 +442,7 @@ static VALUE
|
|
|
442
442
|
mkr_node_set_content(VALUE self, VALUE rb_text)
|
|
443
443
|
{
|
|
444
444
|
lxb_dom_node_t *node = mkr_node_unwrap_mutable(self);
|
|
445
|
-
|
|
445
|
+
mkr_ruby_borrowed_data_t tv = mkr_ruby_verified_data(rb_text, "node content");
|
|
446
446
|
lxb_status_t st = lxb_dom_node_text_content_set(
|
|
447
447
|
node, (const lxb_char_t *)tv.ptr, tv.len);
|
|
448
448
|
RB_GC_GUARD(tv.value);
|
|
@@ -584,7 +584,7 @@ static VALUE
|
|
|
584
584
|
mkr_doc_create_text_node(VALUE self, VALUE rb_text)
|
|
585
585
|
{
|
|
586
586
|
lxb_dom_document_t *doc = mkr_html_doc_unwrap(self);
|
|
587
|
-
|
|
587
|
+
mkr_ruby_borrowed_data_t tv = mkr_ruby_verified_data(rb_text, "text content");
|
|
588
588
|
lxb_dom_text_t *t = lxb_dom_document_create_text_node(
|
|
589
589
|
doc, (const lxb_char_t *)tv.ptr, tv.len);
|
|
590
590
|
RB_GC_GUARD(tv.value);
|
|
@@ -598,7 +598,7 @@ static VALUE
|
|
|
598
598
|
mkr_doc_create_comment(VALUE self, VALUE rb_text)
|
|
599
599
|
{
|
|
600
600
|
lxb_dom_document_t *doc = mkr_html_doc_unwrap(self);
|
|
601
|
-
|
|
601
|
+
mkr_ruby_borrowed_data_t tv = mkr_ruby_verified_data(rb_text, "comment content");
|
|
602
602
|
lxb_dom_comment_t *c = lxb_dom_document_create_comment(
|
|
603
603
|
doc, (const lxb_char_t *)tv.ptr, tv.len);
|
|
604
604
|
RB_GC_GUARD(tv.value);
|
|
@@ -258,6 +258,18 @@ mkr_limit_ast_node(mkr_xpath_limits_t *L, mkr_xpath_error_t *err)
|
|
|
258
258
|
return 0;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/*
|
|
262
|
+
* THE evaluator progress gate. This is the single primitive that bounds runtime
|
|
263
|
+
* work: every loop in the engine whose trip count is input-derived charges ONE
|
|
264
|
+
* tick per iteration through here (the axis walk per visited node, the M*N
|
|
265
|
+
* compare per pair, the index-bucket scan per element, eval_node per AST node).
|
|
266
|
+
* One uniform rule - "input-bounded loop => one eval_op per step" - so checking
|
|
267
|
+
* the DoS bound is local: confirm each such loop calls this. Kept deliberately
|
|
268
|
+
* uniform (no bulk/up-front variant): a bulk charge would only suit
|
|
269
|
+
* run-to-completion loops and would wrongly reject an early-exiting query if
|
|
270
|
+
* misapplied, trading one foot-gun-free rule for a conditional one. Overrun is
|
|
271
|
+
* fail-closed (MKR_XPATH_ERR_LIMIT), never a truncated result.
|
|
272
|
+
*/
|
|
261
273
|
int
|
|
262
274
|
mkr_limit_eval_op(mkr_xpath_limits_t *L, mkr_xpath_error_t *err)
|
|
263
275
|
{
|
|
@@ -1509,14 +1509,15 @@ eval_fncall(mkr_xpath_context_t *ctx, const mkr_node_t *n,
|
|
|
1509
1509
|
mkr_val_t *out, mkr_xpath_error_t *err)
|
|
1510
1510
|
{
|
|
1511
1511
|
const char *ns_uri = NULL;
|
|
1512
|
+
size_t ns_uri_len = 0;
|
|
1512
1513
|
if (n->u.fncall.prefix.ptr) {
|
|
1513
|
-
ns_uri = mkr_ctx_lookup_ns(ctx, n->u.fncall.prefix.ptr, n->u.fncall.prefix.len,
|
|
1514
|
+
ns_uri = mkr_ctx_lookup_ns(ctx, n->u.fncall.prefix.ptr, n->u.fncall.prefix.len, &ns_uri_len);
|
|
1514
1515
|
if (ns_uri == NULL) {
|
|
1515
1516
|
mkr_err_setf(err, MKR_XPATH_ERR_RUNTIME, "unknown namespace prefix '%s'", n->u.fncall.prefix.ptr);
|
|
1516
1517
|
return -1;
|
|
1517
1518
|
}
|
|
1518
1519
|
}
|
|
1519
|
-
mkr_func_impl_t fn = mkr_lookup_function(ns_uri, n->u.fncall.name.ptr);
|
|
1520
|
+
mkr_func_impl_t fn = mkr_lookup_function(ns_uri, ns_uri_len, n->u.fncall.name.ptr, n->u.fncall.name.len);
|
|
1520
1521
|
|
|
1521
1522
|
/* Evaluate arguments once and reuse for either path. */
|
|
1522
1523
|
mkr_val_t *args = NULL;
|
|
@@ -1666,22 +1667,30 @@ eval_node(mkr_xpath_context_t *ctx, const mkr_node_t *n,
|
|
|
1666
1667
|
mkr_val_t *out, mkr_xpath_error_t *err)
|
|
1667
1668
|
{
|
|
1668
1669
|
/* Budget + recursion bookkeeping. Every AST node visit counts as one
|
|
1669
|
-
* eval op; recursion depth tracks how deep we are in expression
|
|
1670
|
-
*
|
|
1670
|
+
* eval op; recursion depth tracks how deep we are in expression nodes
|
|
1671
|
+
* so we abort cleanly on pathological inputs.
|
|
1672
|
+
*
|
|
1673
|
+
* eval_node is the engine's ONLY recursive function, so "AST-driven
|
|
1674
|
+
* recursion is bounded" reduces entirely to this one enter/leave pair:
|
|
1675
|
+
* charge one op + one recursion level on entry, release the level at the
|
|
1676
|
+
* SINGLE exit below. Keeping eval_node single-exit is what makes that
|
|
1677
|
+
* balance locally verifiable - there is no path from the enter here to a
|
|
1678
|
+
* return that skips the mkr_limit_recurse_leave. Do not add an early
|
|
1679
|
+
* `return` between here and the leave; set `rc` and fall through. */
|
|
1671
1680
|
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
1672
1681
|
if (mkr_limit_eval_op(L, err) != 0) return -1;
|
|
1673
1682
|
if (mkr_limit_recurse_enter(L, err) != 0) return -1;
|
|
1674
1683
|
|
|
1684
|
+
int rc;
|
|
1685
|
+
|
|
1675
1686
|
/* Hoisting fast path: a CI subtree that's already been computed in
|
|
1676
1687
|
* this evaluate is returned as a clone. The clone keeps ownership
|
|
1677
1688
|
* semantics clean - mkr_val_clear on either copy is safe. */
|
|
1678
1689
|
if (n->is_context_independent && n->memoized) {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
return rc;
|
|
1690
|
+
rc = mkr_val_clone(&n->memo_value, out, err);
|
|
1691
|
+
goto done; /* single exit: the recurse_leave below still runs */
|
|
1682
1692
|
}
|
|
1683
1693
|
|
|
1684
|
-
int rc;
|
|
1685
1694
|
switch (n->kind) {
|
|
1686
1695
|
case MKR_NK_LITERAL_STR: {
|
|
1687
1696
|
mkr_owned_text_t text;
|
|
@@ -1772,6 +1781,7 @@ eval_node(mkr_xpath_context_t *ctx, const mkr_node_t *n,
|
|
|
1772
1781
|
}
|
|
1773
1782
|
}
|
|
1774
1783
|
|
|
1784
|
+
done:
|
|
1775
1785
|
mkr_limit_recurse_leave(L);
|
|
1776
1786
|
return rc;
|
|
1777
1787
|
}
|
|
@@ -329,13 +329,17 @@ fn_nokogiri_local_name_is(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
|
329
329
|
|
|
330
330
|
typedef struct {
|
|
331
331
|
const char *name;
|
|
332
|
-
|
|
332
|
+
size_t name_len;
|
|
333
|
+
mkr_func_impl_t fn;
|
|
333
334
|
} fn_entry_t;
|
|
334
335
|
|
|
336
|
+
#define FN_ENTRY(name, fn) { (name), sizeof(name) - 1, (fn) }
|
|
337
|
+
#define FN_ENTRY_END { NULL, 0, NULL }
|
|
338
|
+
|
|
335
339
|
static const fn_entry_t fn_table_nokogiri_builtin[] = {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
340
|
+
FN_ENTRY("css-class", fn_nokogiri_css_class),
|
|
341
|
+
FN_ENTRY("local-name-is", fn_nokogiri_local_name_is),
|
|
342
|
+
FN_ENTRY_END,
|
|
339
343
|
};
|
|
340
344
|
|
|
341
345
|
#ifdef MKR_HOST_XML
|
|
@@ -1047,58 +1051,61 @@ fn_lang(mkr_xpath_context_t *ctx, const mkr_focus_t *focus,
|
|
|
1047
1051
|
|
|
1048
1052
|
static const fn_entry_t fn_table[] = {
|
|
1049
1053
|
/* node-set */
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1054
|
+
FN_ENTRY("last", fn_last),
|
|
1055
|
+
FN_ENTRY("position", fn_position),
|
|
1056
|
+
FN_ENTRY("count", fn_count),
|
|
1057
|
+
FN_ENTRY("id", fn_id),
|
|
1058
|
+
FN_ENTRY("local-name", fn_local_name),
|
|
1059
|
+
FN_ENTRY("namespace-uri", fn_namespace_uri),
|
|
1060
|
+
FN_ENTRY("name", fn_name),
|
|
1057
1061
|
/* string */
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1062
|
+
FN_ENTRY("string", fn_string),
|
|
1063
|
+
FN_ENTRY("concat", fn_concat),
|
|
1064
|
+
FN_ENTRY("starts-with", fn_starts_with),
|
|
1065
|
+
FN_ENTRY("contains", fn_contains),
|
|
1066
|
+
FN_ENTRY("substring-before", fn_substring_before),
|
|
1067
|
+
FN_ENTRY("substring-after", fn_substring_after),
|
|
1068
|
+
FN_ENTRY("substring", fn_substring),
|
|
1069
|
+
FN_ENTRY("string-length", fn_string_length),
|
|
1070
|
+
FN_ENTRY("normalize-space", fn_normalize_space),
|
|
1071
|
+
FN_ENTRY("translate", fn_translate),
|
|
1068
1072
|
/* boolean */
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1073
|
+
FN_ENTRY("not", fn_not),
|
|
1074
|
+
FN_ENTRY("true", fn_true),
|
|
1075
|
+
FN_ENTRY("false", fn_false),
|
|
1076
|
+
FN_ENTRY("boolean", fn_boolean),
|
|
1077
|
+
FN_ENTRY("lang", fn_lang),
|
|
1074
1078
|
/* number */
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1079
|
+
FN_ENTRY("number", fn_number),
|
|
1080
|
+
FN_ENTRY("sum", fn_sum),
|
|
1081
|
+
FN_ENTRY("floor", fn_floor),
|
|
1082
|
+
FN_ENTRY("ceiling", fn_ceiling),
|
|
1083
|
+
FN_ENTRY("round", fn_round),
|
|
1080
1084
|
#ifdef MKR_HOST_XML
|
|
1081
1085
|
/* CSS-only internal hooks for untyped :*-of-type; the \x01-prefixed names are
|
|
1082
1086
|
* unreachable from a user XPath expression (the lexer cannot produce them). */
|
|
1083
|
-
|
|
1084
|
-
|
|
1087
|
+
FN_ENTRY(MKR_FN_OF_TYPE_POS, fn_xml_of_type_pos),
|
|
1088
|
+
FN_ENTRY(MKR_FN_OF_TYPE_POS_LAST, fn_xml_of_type_pos_last),
|
|
1085
1089
|
#endif
|
|
1086
|
-
|
|
1090
|
+
FN_ENTRY_END,
|
|
1087
1091
|
};
|
|
1088
1092
|
|
|
1089
1093
|
/* File-static: the function table is engine-internal. eval_fncall (later in the
|
|
1090
1094
|
* merged engine TU) resolves through it; nothing outside the engine does. */
|
|
1091
1095
|
static mkr_func_impl_t
|
|
1092
|
-
mkr_lookup_function(const char *ns_uri,
|
|
1096
|
+
mkr_lookup_function(const char *ns_uri, size_t ns_uri_len,
|
|
1097
|
+
const char *local_name, size_t local_name_len)
|
|
1093
1098
|
{
|
|
1094
1099
|
if (local_name == NULL) return NULL;
|
|
1095
1100
|
if (ns_uri != NULL) {
|
|
1096
1101
|
/* Nokogiri-compatible builtins live in MKR_NS_NOKOGIRI_BUILTIN_URI.
|
|
1097
1102
|
* Other registered namespaces resolve to user-defined functions -
|
|
1098
1103
|
* not yet implemented (Phase 3 TODO: custom function registry). */
|
|
1099
|
-
if (
|
|
1104
|
+
if (mkr_bytes_eq(ns_uri, ns_uri_len, MKR_NS_NOKOGIRI_BUILTIN_URI,
|
|
1105
|
+
sizeof(MKR_NS_NOKOGIRI_BUILTIN_URI) - 1)) {
|
|
1100
1106
|
for (size_t i = 0; fn_table_nokogiri_builtin[i].name; ++i) {
|
|
1101
|
-
if (
|
|
1107
|
+
if (mkr_bytes_eq(fn_table_nokogiri_builtin[i].name, fn_table_nokogiri_builtin[i].name_len,
|
|
1108
|
+
local_name, local_name_len)) {
|
|
1102
1109
|
return fn_table_nokogiri_builtin[i].fn;
|
|
1103
1110
|
}
|
|
1104
1111
|
}
|
|
@@ -1107,7 +1114,9 @@ mkr_lookup_function(const char *ns_uri, const char *local_name)
|
|
|
1107
1114
|
}
|
|
1108
1115
|
/* Default namespace - XPath 1.0 standard library. */
|
|
1109
1116
|
for (size_t i = 0; fn_table[i].name; ++i) {
|
|
1110
|
-
if (
|
|
1117
|
+
if (mkr_bytes_eq(fn_table[i].name, fn_table[i].name_len, local_name, local_name_len)) {
|
|
1118
|
+
return fn_table[i].fn;
|
|
1119
|
+
}
|
|
1111
1120
|
}
|
|
1112
1121
|
return NULL;
|
|
1113
1122
|
}
|
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
#include "mkr_xpath_internal.h"
|
|
10
10
|
#include "../xml/mkr_xml.h"
|
|
11
11
|
|
|
12
|
-
/* Evaluate +expr+ (
|
|
13
|
-
* node-set count, or SIZE_MAX on any
|
|
14
|
-
* "d" -> "urn:d".
|
|
15
|
-
* strlen - this file is outside the clint core+bridge allowlist). */
|
|
12
|
+
/* Evaluate +expr+ (a verified-text token over a compile-time literal) against
|
|
13
|
+
* the XML +doc+ and return the resulting node-set count, or SIZE_MAX on any
|
|
14
|
+
* error / non-node-set. Registers prefix "d" -> "urn:d". */
|
|
16
15
|
static size_t
|
|
17
|
-
xml_count(mkr_xml_doc_t *doc,
|
|
16
|
+
xml_count(mkr_xml_doc_t *doc, mkr_verified_text_t expr)
|
|
18
17
|
{
|
|
19
18
|
/* The context's "document" and root context node are both the DOCUMENT node
|
|
20
19
|
* (the XPath "/" root); for XML the engine's namespace services ignore the
|
|
@@ -24,13 +23,11 @@ xml_count(mkr_xml_doc_t *doc, const char *expr, size_t elen)
|
|
|
24
23
|
if (ctx == NULL) return (size_t)-1;
|
|
25
24
|
mkr_xpath_set_engine_kind(ctx, 1);
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
mkr_xpath_register_ns(ctx, dp, du);
|
|
26
|
+
mkr_xpath_register_ns(ctx, mkr_verified_text_lit("d"), mkr_verified_text_lit("urn:d"));
|
|
29
27
|
|
|
30
28
|
mkr_xpath_limits_t *L = mkr_ctx_limits(ctx);
|
|
31
29
|
mkr_xpath_error_t err = {0};
|
|
32
|
-
|
|
33
|
-
mkr_node_t *ast = mkr_parse(e, L, &err);
|
|
30
|
+
mkr_node_t *ast = mkr_parse(expr, L, &err);
|
|
34
31
|
size_t out = (size_t)-1;
|
|
35
32
|
if (ast != NULL) {
|
|
36
33
|
mkr_xpath_value_t v = {0};
|
|
@@ -57,7 +54,7 @@ mkr_xml_xpath_selftest(void)
|
|
|
57
54
|
if (doc == NULL) return ++idx; /* 1 */
|
|
58
55
|
|
|
59
56
|
#define CHECK(expr, want) \
|
|
60
|
-
do { idx++; if (xml_count(doc, (expr)
|
|
57
|
+
do { idx++; if (xml_count(doc, mkr_verified_text_lit(expr)) != (size_t)(want)) { \
|
|
61
58
|
mkr_xml_doc_destroy(doc); return idx; } } while (0)
|
|
62
59
|
|
|
63
60
|
CHECK("//b", 2); /* 1: unprefixed -> no-namespace only (excludes d:b) */
|
data/lib/makiri/version.rb
CHANGED
data/script/check_c_safety.rb
CHANGED
|
@@ -83,6 +83,17 @@ RULES = [
|
|
|
83
83
|
# buffer unnoticed.
|
|
84
84
|
regex: /\(\s*mkr_verified_text_t\s*\)\s*\{|\bmkr_verified_text_t\s+\w+\s*=\s*\{/
|
|
85
85
|
),
|
|
86
|
+
Rule.new(
|
|
87
|
+
id: "borrowed_text_forge",
|
|
88
|
+
message: "mkr_borrowed_text_t must be built via mkr_borrowed_text()/mkr_borrowed_text_lit()/mkr_borrowed_text_from_*, not a raw struct literal",
|
|
89
|
+
# Same two forge shapes as verified_text_forge. Not a capability guard (a raw
|
|
90
|
+
# literal is semantically identical to the constructor); it funnels the
|
|
91
|
+
# "asserted-valid borrowed slice" through the single named constructor so the
|
|
92
|
+
# assertion stays auditable, matching mkr_text.h's stated intent ("replacing
|
|
93
|
+
# scattered casts"). core/** - where the constructors live - is globally
|
|
94
|
+
# ignore-pathed, so the definitions in mkr_text.h are exempt automatically.
|
|
95
|
+
regex: /\(\s*mkr_borrowed_text_t\s*\)\s*\{|\bmkr_borrowed_text_t\s+\w+\s*=\s*\{/
|
|
96
|
+
),
|
|
86
97
|
# --- HTML/XML representation boundary (see docs/html_xml_boundary_hardening) ---
|
|
87
98
|
# These symbols assume one DOM representation; using them outside their
|
|
88
99
|
# representation-correct / kind-checked home is how shared glue (XPath, NodeSet,
|
|
@@ -99,19 +99,16 @@ ignore_paths:
|
|
|
99
99
|
|
|
100
100
|
# --- parser-TU reader discipline (raw_scan_call / raw_cursor_member) ---
|
|
101
101
|
allowlist:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# --- verified_text_forge: the two Ruby-free test entry points. Neither can use
|
|
108
|
-
# mkr_verified_text_from_view (bridge = Ruby boundary), so each mints the
|
|
109
|
-
# token itself and must supply the contract by construction. ---
|
|
110
|
-
- path: ext/makiri/xpath/mkr_xpath_xml_selftest.c
|
|
111
|
-
rule: verified_text_forge
|
|
112
|
-
max: 2
|
|
113
|
-
reason: "Selftest expressions are compile-time string literals: NUL-terminated, NUL-free, valid UTF-8 by construction."
|
|
102
|
+
# --- verified_text_forge: the Ruby-free test entry points that mint over a
|
|
103
|
+
# runtime buffer (not a compile-time literal, which uses
|
|
104
|
+
# mkr_verified_text_lit). Neither can use mkr_verified_text_from_view
|
|
105
|
+
# (bridge = Ruby boundary), so each mints itself and must supply the
|
|
106
|
+
# contract by construction. ---
|
|
114
107
|
- path: ext/makiri/fuzz/xpath_fuzz.c
|
|
115
108
|
rule: verified_text_forge
|
|
116
109
|
max: 1
|
|
117
110
|
reason: "The libFuzzer harness mints over an owned mkr_strndup copy, which supplies NUL-termination and no-interior-NUL; UTF-8 validity is deliberately left to the lexer's strict decoder (its rejection path is fuzz-target behavior)."
|
|
111
|
+
- path: ext/makiri/fuzz/xml_xpath_fuzz.c
|
|
112
|
+
rule: verified_text_forge
|
|
113
|
+
max: 1
|
|
114
|
+
reason: "The expression mint is over an owned mkr_strndup copy, which supplies NUL-termination and no-interior-NUL; UTF-8 validity is pre-checked (the harness filters invalid UTF-8 before parsing). Mirrors xpath_fuzz.c."
|
data/suppressions/ruby.supp
CHANGED
|
@@ -125,6 +125,28 @@
|
|
|
125
125
|
fun:mark_roots
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
# ---- incremental GC write barrier: uninitialised-value reads ----
|
|
129
|
+
# An allocating Ruby C-API call from one of our cfuncs (e.g. rb_str_subseq in
|
|
130
|
+
# mkr_xml_decode_input) fires the incremental-GC write barrier synchronously;
|
|
131
|
+
# `gc_writebarrier_incremental` then reads an object's not-yet-written RVALUE
|
|
132
|
+
# flag word via `RVALUE_BLACK_P` - the same uninit-flag false positive as the GC
|
|
133
|
+
# drivers above, but reached through the barrier rather than a mark/sweep cycle,
|
|
134
|
+
# so the driver-anchored entries miss it. Our code never touches RVALUE flags;
|
|
135
|
+
# it only calls the allocating API, so anchoring on the barrier cannot mask a
|
|
136
|
+
# real uninit read of ours.
|
|
137
|
+
{
|
|
138
|
+
ruby-gc-cond-writebarrier_incremental
|
|
139
|
+
Memcheck:Cond
|
|
140
|
+
...
|
|
141
|
+
fun:gc_writebarrier_incremental
|
|
142
|
+
}
|
|
143
|
+
{
|
|
144
|
+
ruby-gc-value8-writebarrier_incremental
|
|
145
|
+
Memcheck:Value8
|
|
146
|
+
...
|
|
147
|
+
fun:gc_writebarrier_incremental
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
# ---- Ruby VM internal caches that are never freed before exit ----
|
|
129
151
|
# The global constant-cache / inline method-cache id_table is allocated lazily
|
|
130
152
|
# on method lookup and lives for the life of the VM, so RUBY_FREE_AT_EXIT still
|
data/verify/Makefile
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Verification build for the XML + XPath carve-out.
|
|
2
|
+
#
|
|
3
|
+
# Fully separate from the gem's extconf build: nothing here touches ext/'s
|
|
4
|
+
# Makefile or object files, and no Ruby VM or Lexbor library is linked (the
|
|
5
|
+
# only Lexbor pieces are UNMODIFIED vendor encoding sources - the vanilla
|
|
6
|
+
# constraint is about not patching, not about not compiling).
|
|
7
|
+
#
|
|
8
|
+
# Targets:
|
|
9
|
+
# make smoke - build every CBMC harness as a plain executable and run it
|
|
10
|
+
# (the link check: CBMC treats a missing function body as a
|
|
11
|
+
# nondet return, so only the linker catches a wrong source list)
|
|
12
|
+
# make selftest - build + run the carve-out selftest (XML parse + XPath eval,
|
|
13
|
+
# no Ruby, no Lexbor lib)
|
|
14
|
+
# make cbmc - the proven set (alloc/utf8/utf8-chain/utf8-words/span/
|
|
15
|
+
# spanbuf/buf/hash/xml-chars/xpath-number; ~8 min total)
|
|
16
|
+
# make cbmc-deep - the experimental set (xpath-lex/xpath-parse/xml-parse/
|
|
17
|
+
# xml-expand).
|
|
18
|
+
# These do NOT converge in practical time on current
|
|
19
|
+
# bit-level solvers (measured: >10 min each even at 3-4
|
|
20
|
+
# byte inputs, with MiniSat, kissat and Bitwuzla alike);
|
|
21
|
+
# kept as tuning knobs for future solver/harness work,
|
|
22
|
+
# e.g. make cbmc-xpath-parse XPATH_EXPR_MAX=4 XPATH_UNWIND=12
|
|
23
|
+
#
|
|
24
|
+
# Native libFuzzer targets live in ext/makiri/fuzz/ (their own Makefile + the
|
|
25
|
+
# nightly libfuzzer.yml workflow), not here - this directory is proofs only.
|
|
26
|
+
|
|
27
|
+
ROOT := ..
|
|
28
|
+
EXT := $(ROOT)/ext/makiri
|
|
29
|
+
LXB := $(ROOT)/vendor/lexbor/source
|
|
30
|
+
|
|
31
|
+
CC ?= cc
|
|
32
|
+
CBMC ?= cbmc
|
|
33
|
+
CFLAGS ?= -O1 -g -Wall -Wextra
|
|
34
|
+
# fmod & co. need an explicit -lm on Linux (folded into libc on macOS).
|
|
35
|
+
LDLIBS ?= -lm
|
|
36
|
+
INC := -I $(EXT) -I $(LXB) -I .
|
|
37
|
+
|
|
38
|
+
CORE_SRC := $(EXT)/core/mkr_alloc.c $(EXT)/core/mkr_buf.c \
|
|
39
|
+
$(EXT)/core/mkr_core.c $(EXT)/core/mkr_utf8.c
|
|
40
|
+
XML_SRC := $(EXT)/xml/mkr_xml_chars.c $(EXT)/xml/mkr_xml_index.c \
|
|
41
|
+
$(EXT)/xml/mkr_xml_mutate.c $(EXT)/xml/mkr_xml_node.c \
|
|
42
|
+
$(EXT)/xml/mkr_xml_tree.c
|
|
43
|
+
# Lexer/parser/number + shared support: what mkr_parse reaches.
|
|
44
|
+
XPATH_FRONT_SRC := $(EXT)/xpath/mkr_xpath_lex.c $(EXT)/xpath/mkr_xpath_parse.c \
|
|
45
|
+
$(EXT)/xpath/mkr_xpath_shared.c $(EXT)/xpath/mkr_xpath_number.c
|
|
46
|
+
# The full engine: dispatcher + the XML monomorphization (HTML side is stub.c).
|
|
47
|
+
XPATH_FULL_SRC := $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c \
|
|
48
|
+
$(EXT)/xpath/mkr_xpath_engine_xml.c
|
|
49
|
+
# Unmodified vendor sources for lxb_encoding_decode_valid_utf_8_single.
|
|
50
|
+
LXB_ENC_SRC := $(LXB)/lexbor/encoding/decode.c $(LXB)/lexbor/encoding/multi_res.c \
|
|
51
|
+
$(LXB)/lexbor/encoding/range_res.c $(LXB)/lexbor/encoding/single_res.c
|
|
52
|
+
|
|
53
|
+
ENGINE_SRC := $(CORE_SRC) $(XML_SRC) $(XPATH_FULL_SRC) $(LXB_ENC_SRC) stub.c
|
|
54
|
+
|
|
55
|
+
BUILD := build
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------- smoke + selftest
|
|
58
|
+
|
|
59
|
+
HARNESSES := alloc utf8 utf8-chain utf8-words span spanbuf buf hash xml-chars xml-expand xpath-number xpath-lex xpath-parse xml-parse
|
|
60
|
+
|
|
61
|
+
smoke: $(HARNESSES:%=$(BUILD)/harness-%)
|
|
62
|
+
@for h in $^; do echo "== $$h"; $$h || exit 1; done
|
|
63
|
+
@echo "smoke: all harnesses link and run"
|
|
64
|
+
|
|
65
|
+
$(BUILD)/harness-alloc: harness_alloc.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
66
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
67
|
+
$(BUILD)/harness-utf8: harness_utf8.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
68
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
69
|
+
$(BUILD)/harness-utf8-chain: harness_utf8_chain.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
70
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
71
|
+
$(BUILD)/harness-utf8-words: harness_utf8_words.c $(EXT)/core/mkr_utf8.c | $(BUILD)
|
|
72
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
73
|
+
$(BUILD)/harness-span: harness_span.c | $(BUILD)
|
|
74
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
75
|
+
$(BUILD)/harness-spanbuf: harness_spanbuf.c | $(BUILD)
|
|
76
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
77
|
+
$(BUILD)/harness-buf: harness_buf.c $(EXT)/core/mkr_buf.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
78
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
79
|
+
$(BUILD)/harness-hash: harness_hash.c | $(BUILD)
|
|
80
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
81
|
+
$(BUILD)/harness-xpath-number: harness_xpath_number.c $(EXT)/xpath/mkr_xpath_number.c $(EXT)/core/mkr_alloc.c | $(BUILD)
|
|
82
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
83
|
+
# mkr_parse's limit checks + limits_init_defaults live in mkr_xpath.c (the
|
|
84
|
+
# dispatcher), which references both engine instances - so the smoke link
|
|
85
|
+
# takes the full engine (stub.c supplies the HTML side).
|
|
86
|
+
$(BUILD)/harness-xpath-parse: harness_xpath_parse.c $(ENGINE_SRC) | $(BUILD)
|
|
87
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
88
|
+
$(BUILD)/harness-xpath-lex: harness_xpath_lex.c $(ENGINE_SRC) | $(BUILD)
|
|
89
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
90
|
+
$(BUILD)/harness-xml-parse: harness_xml_parse.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
91
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
92
|
+
$(BUILD)/harness-xml-chars: harness_xml_chars.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
93
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
94
|
+
$(BUILD)/harness-xml-expand: harness_xml_expand.c $(XML_SRC) $(CORE_SRC) | $(BUILD)
|
|
95
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
96
|
+
|
|
97
|
+
selftest: $(BUILD)/selftest
|
|
98
|
+
$(BUILD)/selftest
|
|
99
|
+
|
|
100
|
+
$(BUILD)/selftest: selftest_main.c $(ENGINE_SRC) $(EXT)/xpath/mkr_xpath_xml_selftest.c | $(BUILD)
|
|
101
|
+
$(CC) $(CFLAGS) $(INC) $^ -o $@ $(LDLIBS)
|
|
102
|
+
|
|
103
|
+
$(BUILD):
|
|
104
|
+
mkdir -p $(BUILD)
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------- CBMC proofs
|
|
107
|
+
#
|
|
108
|
+
# Every proof gets --bounds-check --pointer-check (memory safety) and
|
|
109
|
+
# --signed-overflow-check (UB); --unsigned-overflow-check is added ONLY where
|
|
110
|
+
# wrapping would be a bug (the size arithmetic) - the UTF-8 validator's
|
|
111
|
+
# word-at-a-time scan and hash-style code wrap on purpose, and unsigned wrap
|
|
112
|
+
# is defined C. --malloc-may-fail --malloc-fail-null verifies the fail-closed
|
|
113
|
+
# OOM branches. Unwind bounds are per-harness knobs; --unwinding-assertions
|
|
114
|
+
# turns "too small a bound" into a failure instead of a silent partial proof.
|
|
115
|
+
|
|
116
|
+
CBMC_MEM := --bounds-check --pointer-check --signed-overflow-check \
|
|
117
|
+
--div-by-zero-check --unwinding-assertions --object-bits 12
|
|
118
|
+
CBMC_OOM := --malloc-may-fail --malloc-fail-null
|
|
119
|
+
# libc bodies CBMC lacks on this platform (see the file's comment).
|
|
120
|
+
CBMC_MODELS := cbmc_models.c
|
|
121
|
+
|
|
122
|
+
# mkr_grow_capacity doubles from 8, so <= ~60 iterations before the cap check.
|
|
123
|
+
ALLOC_UNWIND ?= 64
|
|
124
|
+
cbmc-alloc:
|
|
125
|
+
$(CBMC) harness_alloc.c $(CBMC_MODELS) $(EXT)/core/mkr_alloc.c $(INC) \
|
|
126
|
+
$(CBMC_MEM) $(CBMC_OOM) --unsigned-overflow-check --conversion-check \
|
|
127
|
+
--unwind $(ALLOC_UNWIND)
|
|
128
|
+
|
|
129
|
+
UTF8_UNWIND ?= 12
|
|
130
|
+
cbmc-utf8:
|
|
131
|
+
$(CBMC) harness_utf8.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
132
|
+
$(CBMC_MEM) --unwind $(UTF8_UNWIND)
|
|
133
|
+
|
|
134
|
+
# Chain length and unwind grow together (unwind must exceed the byte count;
|
|
135
|
+
# see the harness header for the measured length/time trade-off).
|
|
136
|
+
UTF8_CHAIN_MAX ?= 12
|
|
137
|
+
UTF8_CHAIN_UNWIND ?= 16
|
|
138
|
+
cbmc-utf8-chain:
|
|
139
|
+
$(CBMC) harness_utf8_chain.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
140
|
+
-DVERIFY_UTF8_CHAIN_MAX=$(UTF8_CHAIN_MAX) \
|
|
141
|
+
$(CBMC_MEM) --unwind $(UTF8_CHAIN_UNWIND)
|
|
142
|
+
|
|
143
|
+
cbmc-utf8-words:
|
|
144
|
+
$(CBMC) harness_utf8_words.c $(CBMC_MODELS) $(EXT)/core/mkr_utf8.c $(INC) \
|
|
145
|
+
$(CBMC_MEM) --unwind 20
|
|
146
|
+
|
|
147
|
+
SPAN_UNWIND ?= 12
|
|
148
|
+
cbmc-span:
|
|
149
|
+
$(CBMC) harness_span.c $(CBMC_MODELS) $(INC) \
|
|
150
|
+
$(CBMC_MEM) --unwind $(SPAN_UNWIND)
|
|
151
|
+
|
|
152
|
+
cbmc-spanbuf:
|
|
153
|
+
$(CBMC) harness_spanbuf.c $(CBMC_MODELS) $(INC) \
|
|
154
|
+
$(CBMC_MEM) --unwind $(SPAN_UNWIND)
|
|
155
|
+
|
|
156
|
+
cbmc-buf:
|
|
157
|
+
$(CBMC) harness_buf.c $(CBMC_MODELS) $(EXT)/core/mkr_buf.c $(EXT)/core/mkr_alloc.c $(INC) \
|
|
158
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind 16
|
|
159
|
+
|
|
160
|
+
# The sizer only shifts, so unlike the mul/div guards this closes at full
|
|
161
|
+
# width; the loop runs at most one step per size_t bit.
|
|
162
|
+
HASH_UNWIND ?= 66
|
|
163
|
+
cbmc-hash:
|
|
164
|
+
$(CBMC) harness_hash.c $(CBMC_MODELS) $(INC) \
|
|
165
|
+
$(CBMC_MEM) --unwind $(HASH_UNWIND)
|
|
166
|
+
|
|
167
|
+
# XML character classes: predicate inclusions over all code points + the
|
|
168
|
+
# validators cross-checked against the independent UTF-8 validator. CBMC only
|
|
169
|
+
# needs mkr_xml_chars.c + mkr_utf8.c (the other xml/ entries the header
|
|
170
|
+
# declares stay bodyless - unreachable from this harness).
|
|
171
|
+
XML_CHARS_UNWIND ?= 12
|
|
172
|
+
cbmc-xml-chars:
|
|
173
|
+
$(CBMC) harness_xml_chars.c $(CBMC_MODELS) $(EXT)/xml/mkr_xml_chars.c $(EXT)/core/mkr_utf8.c $(INC) \
|
|
174
|
+
$(CBMC_MEM) --unwind $(XML_CHARS_UNWIND)
|
|
175
|
+
|
|
176
|
+
# The XPath Number scanner/converter. The cbmc_models.c strtod ("conversion
|
|
177
|
+
# unavailable") steers from_extent down its own fallback chain - the isolating
|
|
178
|
+
# reparse and the hand assembly, i.e. exactly our code - on every input; the
|
|
179
|
+
# libc-success path is trusted, not verified (like memcpy).
|
|
180
|
+
cbmc-xpath-number:
|
|
181
|
+
$(CBMC) harness_xpath_number.c $(CBMC_MODELS) $(EXT)/xpath/mkr_xpath_number.c $(EXT)/core/mkr_alloc.c $(INC) \
|
|
182
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind 12
|
|
183
|
+
|
|
184
|
+
cbmc: cbmc-alloc cbmc-utf8 cbmc-utf8-chain cbmc-utf8-words cbmc-span cbmc-spanbuf cbmc-buf cbmc-hash cbmc-xml-chars cbmc-xpath-number
|
|
185
|
+
@echo "cbmc: all proofs passed"
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------- experimental (cbmc-deep)
|
|
188
|
+
#
|
|
189
|
+
# The whole-entry parser/lexer proofs below are formulated and smoke-checked,
|
|
190
|
+
# but no current solver closes them in practical time (see the header). CBMC
|
|
191
|
+
# gets mkr_xpath.c for the limit checks but NOT the engine instances or the
|
|
192
|
+
# vendor encoding tables (nothing there is reachable from mkr_parse; the eval
|
|
193
|
+
# entries mkr_xpath.c references stay bodyless - unreachable from these
|
|
194
|
+
# harnesses). Expression/input length and unwind grow together.
|
|
195
|
+
|
|
196
|
+
XPATH_EXPR_MAX ?= 6
|
|
197
|
+
XPATH_UNWIND ?= 16
|
|
198
|
+
cbmc-xpath-lex:
|
|
199
|
+
$(CBMC) harness_xpath_lex.c $(CBMC_MODELS) $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c $(CORE_SRC) $(INC) \
|
|
200
|
+
-DVERIFY_XPATH_EXPR_MAX=$(XPATH_EXPR_MAX) \
|
|
201
|
+
$(CBMC_MEM) --unwind $(XPATH_UNWIND)
|
|
202
|
+
|
|
203
|
+
cbmc-xpath-parse:
|
|
204
|
+
$(CBMC) harness_xpath_parse.c $(CBMC_MODELS) $(XPATH_FRONT_SRC) $(EXT)/xpath/mkr_xpath.c $(CORE_SRC) $(INC) \
|
|
205
|
+
-DVERIFY_XPATH_EXPR_MAX=$(XPATH_EXPR_MAX) \
|
|
206
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind $(XPATH_UNWIND)
|
|
207
|
+
|
|
208
|
+
XML_INPUT_MAX ?= 6
|
|
209
|
+
XML_UNWIND ?= 16
|
|
210
|
+
cbmc-xml-parse:
|
|
211
|
+
$(CBMC) harness_xml_parse.c $(CBMC_MODELS) $(XML_SRC) $(CORE_SRC) $(INC) \
|
|
212
|
+
-DVERIFY_XML_INPUT_MAX=$(XML_INPUT_MAX) \
|
|
213
|
+
$(CBMC_MEM) $(CBMC_OOM) --unwind $(XML_UNWIND)
|
|
214
|
+
|
|
215
|
+
# Entity/char-ref expansion over nondet valid-UTF-8 input (the document it
|
|
216
|
+
# writes into is parsed from a concrete 4-byte input, so only the expansion
|
|
217
|
+
# input is nondet). Does not converge: 7-byte input, --unwind 12, still in
|
|
218
|
+
# BMC when the 420 s alarm killed it (measured; higher unwinds are strictly
|
|
219
|
+
# larger). No $(CBMC_OOM): a NULL doc is treated as early-exit, OOM injection
|
|
220
|
+
# is not what this harness is about.
|
|
221
|
+
# Also measured: 5-byte input still did not converge in 6 min - the cost is
|
|
222
|
+
# the whole-parser+arena state behind the doc handle, not the input length.
|
|
223
|
+
XML_EXPAND_MAX ?= 7
|
|
224
|
+
XML_EXPAND_UNWIND ?= 12
|
|
225
|
+
cbmc-xml-expand:
|
|
226
|
+
$(CBMC) harness_xml_expand.c $(CBMC_MODELS) $(XML_SRC) $(CORE_SRC) $(INC) \
|
|
227
|
+
-DVERIFY_XML_EXPAND_MAX=$(XML_EXPAND_MAX) \
|
|
228
|
+
$(CBMC_MEM) --unwind $(XML_EXPAND_UNWIND)
|
|
229
|
+
|
|
230
|
+
cbmc-deep: cbmc-xpath-lex cbmc-xpath-parse cbmc-xml-parse cbmc-xml-expand
|
|
231
|
+
|
|
232
|
+
clean:
|
|
233
|
+
rm -rf $(BUILD)
|
|
234
|
+
|
|
235
|
+
.PHONY: smoke selftest cbmc cbmc-alloc cbmc-utf8 cbmc-utf8-chain cbmc-utf8-words cbmc-span \
|
|
236
|
+
cbmc-spanbuf cbmc-buf cbmc-hash cbmc-xml-chars cbmc-xpath-number \
|
|
237
|
+
cbmc-deep cbmc-xpath-lex cbmc-xpath-parse cbmc-xml-parse cbmc-xml-expand clean
|