makiri 0.5.1-aarch64-linux → 0.6.0-aarch64-linux

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc9698d035a5e648a62aacda44694b0df5918e88cea61c52a061af4418976c7b
4
- data.tar.gz: aae9379b5390003c757e98e77154c8d36e7620eaf9f0ad5b05a91af8816ba6a9
3
+ metadata.gz: 6c0a020c0e52029940d64a91b53ae459d08b1d53e3106c3b312b6c86601bc29a
4
+ data.tar.gz: 2d5bf5107d4de3892b70684aaad11b67f0f7eec74496e01132f79703db87262e
5
5
  SHA512:
6
- metadata.gz: ef81933c52378d6368a71250397c4dd459a1fe106acf7e5974d96a82f7bee8bf74d0d1e74868d800a1efebfd0dfd8c6e99e82e689e92ce3e8b022f84a1b5bd16
7
- data.tar.gz: 43d7cd497019cc3ed4f119e84d0f1a8608535187ae62f35caaa9cb2034f2021e425f8511e80e642fe861300db5c5041dc16f750767182a103aa0a44c51c0e920
6
+ metadata.gz: 6e0eda64911e48477808cf4b033b8f24fdbcb978558e27db9ea570bcd4364eb45e81e0a39b0c0e3ebe357aff622fb479e0c230b6c218f4f23ab760d3cd163cc8
7
+ data.tar.gz: 96f2da4e924ade762e57887c128bd70583330917a18f09abafd2721c2fdc61f647e5d1eb64544b8af753182c2e83d320b9dd2e02dcd384e3187ac27b3b27a352
@@ -21,7 +21,7 @@ jobs:
21
21
  strategy:
22
22
  fail-fast: false
23
23
  matrix:
24
- target: [xml, xpath]
24
+ target: [xml, xpath, xml_xpath]
25
25
 
26
26
  steps:
27
27
  - name: Checkout (with vendored Lexbor submodule)
@@ -0,0 +1,37 @@
1
+ name: Verify
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ cbmc:
11
+ name: CBMC proofs
12
+ runs-on: ubuntu-24.04
13
+ timeout-minutes: 20
14
+
15
+ steps:
16
+ - name: Checkout (with vendored Lexbor submodule)
17
+ uses: actions/checkout@v6
18
+ with:
19
+ submodules: recursive
20
+
21
+ - name: Set up Ruby
22
+ uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: "3.4"
25
+ bundler-cache: true
26
+
27
+ - name: Install CBMC
28
+ env:
29
+ CBMC_VERSION: "6.9.0"
30
+ run: |
31
+ curl -fsSLO "https://github.com/diffblue/cbmc/releases/download/cbmc-${CBMC_VERSION}/ubuntu-24.04-cbmc-${CBMC_VERSION}-Linux.deb"
32
+ sudo apt-get update
33
+ sudo apt-get install -y "./ubuntu-24.04-cbmc-${CBMC_VERSION}-Linux.deb"
34
+ cbmc --version
35
+
36
+ - name: Run verification suite
37
+ run: bundle exec rake verify
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] - 2026-07-05
4
+
5
+ ### Changed
6
+
7
+ * Text and comment node content (`create_text_node`, `create_comment`,
8
+ `content=`) and attribute values (`[]=`, `set_attribute_ns`) now accept an
9
+ embedded NUL (U+0000) and store it verbatim, matching the WHATWG DOM and
10
+ browsers, instead of raising. Names, tag names, namespaces, CSS selectors, and
11
+ XPath expressions stay NUL-strict, and invalid UTF-8 is still rejected. XML
12
+ documents (`Makiri::XML`) are unchanged and still reject NUL everywhere, since
13
+ XML 1.0 has no legal U+0000 character.
14
+
3
15
  ## [0.5.1] - 2026-06-22
4
16
 
5
17
  ### Changed
data/README.md CHANGED
@@ -267,6 +267,22 @@ Detailed, test-backed notes live in `spec/conformance/README.md`.
267
267
  * Type selectors are ASCII case-insensitive (CSS-correct for HTML; `LI` matches `<li>`)
268
268
  * `Nokogiri::HTML5` is case-sensitive there.
269
269
 
270
+ ### Text input (mutation APIs)
271
+
272
+ * Programmatic string arguments must be **valid UTF-8** (invalid bytes raise
273
+ `Makiri::Error`, never silently repaired - unlike HTML *parsing*, which decodes
274
+ leniently to U+FFFD).
275
+ * An embedded **NUL (U+0000)** is **accepted in HTML data-family content** -
276
+ text/comment node content (`create_text_node`, `create_comment`, `content=`)
277
+ and attribute values (`[]=`, `set_attribute_ns`) - and stored/read back
278
+ verbatim, matching the WHATWG DOM / browsers (`document.createTextNode("\0")`).
279
+ It is still **rejected** in names, tag names, namespaces, PI target/data, CSS
280
+ selectors, and XPath expressions/variable names (a NUL there raises).
281
+ * On re-parse, the HTML tokenizer replaces a U+0000 in text/attributes with
282
+ U+FFFD (WHATWG), so a serialized-then-reparsed round-trip is not byte-identical.
283
+ * `Makiri::XML` rejects NUL everywhere: XML 1.0 has no legal U+0000 character,
284
+ so admitting it would produce non-well-formed XML.
285
+
270
286
  ## Conformance
271
287
 
272
288
  The XPath engine and XML parser are original code, so their correctness is held by
data/Rakefile CHANGED
@@ -287,6 +287,12 @@ task :oom do
287
287
  puts "(injection build left in place; run `rake clean compile` to restore a normal build)"
288
288
  end
289
289
 
290
+ desc "CBMC proofs over the Ruby/Lexbor-free carve-out (core + XML + XPath front; " \
291
+ "needs cbmc; see docs/formal_verification.ja.md and verify/Makefile)"
292
+ task :verify do
293
+ sh "make -C verify smoke selftest cbmc"
294
+ end
295
+
290
296
  desc "Run the performance benchmark (Makiri vs Nokogiri reference)"
291
297
  task bench: :compile do
292
298
  # Run outside the bundle so the bench-only gems (nokogiri, benchmark-ips)
Binary file
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Makiri
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -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
- - path: ext/makiri/xpath/mkr_xpath_funcs_body.h
103
- rule: raw_scan_call
104
- max: 3
105
- reason: "mkr_lookup_function's three strcmp over compile-time function-name tables: both sides are NUL-terminated (table literals / owned AST text), and the lookup signature takes bare const char* from the evaluator."
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."
@@ -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
@@ -0,0 +1,38 @@
1
+ /* Plain-C bodies for libc functions CBMC's built-in library does not model on
2
+ * this platform (a bodyless callee havocs its return value, turning every
3
+ * downstream pointer check into a false failure). Compiled ONLY into the CBMC
4
+ * runs, never into the smoke/selftest/fuzzer builds (those link the real libc).
5
+ * Each body is the obvious reference implementation, so proving against it is
6
+ * proving against the function's contract. */
7
+ #ifdef __CPROVER__
8
+
9
+ #include <stddef.h>
10
+ #include <stdlib.h> /* the SDK's strtod declaration carries an asm alias
11
+ * (_strtod on macOS); defining without it leaves the
12
+ * callee bodyless under its aliased name */
13
+ #include <string.h>
14
+
15
+ void *
16
+ memchr(const void *s, int c, size_t n)
17
+ {
18
+ const unsigned char *p = s;
19
+ for (size_t i = 0; i < n; ++i) {
20
+ if (p[i] == (unsigned char)c) return (void *)(p + i);
21
+ }
22
+ return NULL;
23
+ }
24
+
25
+ /* strtod, modeled as "libc conversion unavailable" (no bytes consumed). This
26
+ * is a deliberate choice, not a shortcut: correctly-rounded decimal parsing is
27
+ * libc territory we trust rather than verify (like memcpy), and this model
28
+ * steers every caller down its own fallback - for mkr_xpath_number_from_extent
29
+ * that means the isolating-reparse and hand-assembly paths, i.e. exactly OUR
30
+ * code, get proven on all inputs. */
31
+ double
32
+ strtod(const char *nptr, char **endptr)
33
+ {
34
+ if (endptr != NULL) *endptr = (char *)nptr;
35
+ return 0.0;
36
+ }
37
+
38
+ #endif /* __CPROVER__ */