makiri 0.5.1-aarch64-linux → 0.7.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 +4 -4
- data/.github/workflows/ci.yml +2 -1
- data/.github/workflows/libfuzzer.yml +1 -1
- data/.github/workflows/release.yml +28 -13
- data/.github/workflows/verify.yml +37 -0
- data/CHANGELOG.md +52 -0
- data/README.md +16 -0
- data/Rakefile +14 -7
- data/lib/makiri/3.2/makiri.so +0 -0
- data/lib/makiri/3.3/makiri.so +0 -0
- data/lib/makiri/3.4/makiri.so +0 -0
- data/lib/makiri/4.0/makiri.so +0 -0
- data/lib/makiri/attr.rb +0 -3
- data/lib/makiri/comment.rb +1 -3
- data/lib/makiri/compat_aliases.rb +1 -12
- data/lib/makiri/version.rb +1 -1
- data/script/check_c_safety.rb +11 -0
- data/script/check_c_safety_allowlist.yml +7 -16
- 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 +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1117686dde2fd5e8a6e863971b3220a7bfffd621e3dea8097d0b0b0b14f75275
|
|
4
|
+
data.tar.gz: ee2fbfb3b456a86f9444bf9faa9ccdf051b224e44ffc951e2b84ef49333e6907
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5fa4fce173089263057caf5f8f13ab305d60c6d07f7df4a2d5b921d7025a25be5ace35c7797e9664938f7537a7ff3c6870cfacd75b6689f3e692f3e9c9d003a8
|
|
7
|
+
data.tar.gz: 97694793f4875a0378582e504b2df12baa4902d18c2b28ea9d3fc9ed78de0da9310af78f67a7a7a5e5298a321477175343fcc34320ea49787450e0951b56a371
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -12,7 +12,7 @@ jobs:
|
|
|
12
12
|
strategy:
|
|
13
13
|
fail-fast: false
|
|
14
14
|
matrix:
|
|
15
|
-
os: [ubuntu-latest, macos-latest]
|
|
15
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
16
|
ruby: ["3.2", "3.3", "3.4", "4.0"]
|
|
17
17
|
|
|
18
18
|
steps:
|
|
@@ -42,6 +42,7 @@ jobs:
|
|
|
42
42
|
THREADING: ${{ (matrix.os == 'ubuntu-latest' && matrix.ruby == '3.4') && '1' || '' }}
|
|
43
43
|
|
|
44
44
|
- name: Smoke-load the gem
|
|
45
|
+
shell: bash
|
|
45
46
|
run: bundle exec ruby -Ilib -r makiri -e 'p Makiri::VERSION'
|
|
46
47
|
|
|
47
48
|
sanitize:
|
|
@@ -62,10 +62,12 @@ jobs:
|
|
|
62
62
|
- x86_64-linux
|
|
63
63
|
- aarch64-linux
|
|
64
64
|
- arm64-darwin
|
|
65
|
+
- x64-mingw-ucrt
|
|
65
66
|
include:
|
|
66
|
-
- { platform: x86_64-linux,
|
|
67
|
-
- { platform: aarch64-linux,
|
|
68
|
-
- { platform: arm64-darwin,
|
|
67
|
+
- { platform: x86_64-linux, os: ubuntu-latest }
|
|
68
|
+
- { platform: aarch64-linux, os: ubuntu-24.04-arm }
|
|
69
|
+
- { platform: arm64-darwin, os: macos-14 }
|
|
70
|
+
- { platform: x64-mingw-ucrt, os: windows-latest }
|
|
69
71
|
steps:
|
|
70
72
|
- uses: actions/checkout@v6
|
|
71
73
|
with:
|
|
@@ -79,6 +81,7 @@ jobs:
|
|
|
79
81
|
- name: Compile (builds vendored Lexbor, then the ext)
|
|
80
82
|
run: bundle exec rake compile
|
|
81
83
|
- name: Smoke-load
|
|
84
|
+
shell: bash
|
|
82
85
|
run: bundle exec ruby -Ilib -r makiri -e 'p Makiri::VERSION'
|
|
83
86
|
- name: Stage the compiled binary under its ABI subdir
|
|
84
87
|
shell: bash
|
|
@@ -97,16 +100,25 @@ jobs:
|
|
|
97
100
|
run: |
|
|
98
101
|
dlext="$(ruby -e 'print RbConfig::CONFIG["DLEXT"]')"
|
|
99
102
|
bin="lib/makiri/makiri.${dlext}"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
case "$(uname -s)" in
|
|
104
|
+
Darwin)
|
|
105
|
+
if otool -L "$bin" | grep -i 'libruby'; then
|
|
106
|
+
echo "::error::$bin hard-links libruby; precompiled gem would not load on other Ruby installs"; exit 1
|
|
107
|
+
fi
|
|
108
|
+
;;
|
|
109
|
+
MINGW*|MSYS*|CYGWIN*)
|
|
110
|
+
# Windows PE DLLs MUST import the Ruby DLL via the import library
|
|
111
|
+
# (no dynamic_lookup on PE/COFF). A libruby import here is by design,
|
|
112
|
+
# not a portability bug, so this assertion does not apply.
|
|
113
|
+
echo "Windows (x64-mingw-ucrt): Ruby import-library link is by design; skipping libruby assertion"
|
|
114
|
+
;;
|
|
115
|
+
*)
|
|
116
|
+
if readelf -d "$bin" 2>/dev/null | grep -iE 'NEEDED.*libruby'; then
|
|
117
|
+
echo "::error::$bin has a libruby NEEDED entry; precompiled gem may not load on a static Ruby"; exit 1
|
|
118
|
+
fi
|
|
119
|
+
;;
|
|
120
|
+
esac
|
|
121
|
+
echo "OK: portability check complete for $bin"
|
|
110
122
|
- uses: actions/upload-artifact@v7
|
|
111
123
|
with:
|
|
112
124
|
name: bin-${{ matrix.platform }}-${{ matrix.ruby }}
|
|
@@ -128,6 +140,8 @@ jobs:
|
|
|
128
140
|
os: ubuntu-24.04-arm
|
|
129
141
|
- platform: arm64-darwin
|
|
130
142
|
os: macos-14
|
|
143
|
+
- platform: x64-mingw-ucrt
|
|
144
|
+
os: windows-latest
|
|
131
145
|
steps:
|
|
132
146
|
- uses: actions/checkout@v6
|
|
133
147
|
with:
|
|
@@ -157,6 +171,7 @@ jobs:
|
|
|
157
171
|
- name: Build the native gem
|
|
158
172
|
run: ruby script/build_native_gem.rb "${{ matrix.platform }}"
|
|
159
173
|
- name: Verify it installs without recompiling
|
|
174
|
+
shell: bash
|
|
160
175
|
run: |
|
|
161
176
|
gem install "makiri-"*"-${{ matrix.platform }}.gem" --local
|
|
162
177
|
ruby -e 'require "makiri"; puts Makiri::VERSION'
|
|
@@ -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,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-07-11
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* `Document#create_document_type(name, public_id = "", system_id = "")` on both
|
|
8
|
+
`Makiri::HTML::Document` and `Makiri::XML::Document` (DOM
|
|
9
|
+
`DOMImplementation.createDocumentType`): creates a detached `DocumentType`
|
|
10
|
+
node, to be placed before the document element (e.g. with
|
|
11
|
+
`root.add_previous_sibling(doctype)`). The name is case-preserving; an omitted
|
|
12
|
+
or empty public/system id is treated as absent; an invalid name (or a `"` in an
|
|
13
|
+
id) raises. Inserting a doctype is validated against the WHATWG rules — it may
|
|
14
|
+
only be a child of the document, must precede the document element, and a
|
|
15
|
+
document holds at most one — so a misplaced or duplicate doctype raises rather
|
|
16
|
+
than producing a malformed tree.
|
|
17
|
+
|
|
18
|
+
* The XML `DocumentType` is now part of the tree: it appears in
|
|
19
|
+
`Document#children` (before the root, like a browser DOM) and carries
|
|
20
|
+
`parent`/sibling links, for both a parsed `<!DOCTYPE>` and the factory above.
|
|
21
|
+
`Document#internal_subset` still returns it. XPath is unaffected — its 1.0 data
|
|
22
|
+
model excludes the doctype (`//node()` / `/node()` never surface it), as in
|
|
23
|
+
Nokogiri/libxml2. This supports building a document with
|
|
24
|
+
`DOMImplementation.createDocument(namespace, qualifiedName, doctype)`.
|
|
25
|
+
|
|
26
|
+
* `Makiri::XML::Document#create_loose_dom_element(qualified_name, prefix,
|
|
27
|
+
local_name, namespace_uri)`, an escape hatch for browser-DOM interop that
|
|
28
|
+
creates an element whose name follows WHATWG DOM element-name rules rather
|
|
29
|
+
than the stricter XML QName rules (e.g. `"f:o:o"`, `":foo"`, `"0:a"`). The
|
|
30
|
+
regular factories stay XML-strict. Such an element is intentionally not
|
|
31
|
+
XML-serializable: `#to_xml` and `#canonicalize` raise `Makiri::Error` if the
|
|
32
|
+
tree contains one. Renaming it to a valid XML name (`#name=`) returns it to
|
|
33
|
+
strict mode.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
* Replacing an XML node with a `DocumentFragment` (`node.replace(fragment)`) is
|
|
38
|
+
now all-or-nothing: if the replacement is rejected (e.g. it would leave the
|
|
39
|
+
document with two root elements), the target node and the fragment are left
|
|
40
|
+
untouched, instead of dropping the target and splicing in only part of the
|
|
41
|
+
fragment. Replacing the root element with a single-element fragment now works.
|
|
42
|
+
|
|
43
|
+
## [0.6.0] - 2026-07-05
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
* Text and comment node content (`create_text_node`, `create_comment`,
|
|
48
|
+
`content=`) and attribute values (`[]=`, `set_attribute_ns`) now accept an
|
|
49
|
+
embedded NUL (U+0000) and store it verbatim, matching the WHATWG DOM and
|
|
50
|
+
browsers, instead of raising. Names, tag names, namespaces, CSS selectors, and
|
|
51
|
+
XPath expressions stay NUL-strict, and invalid UTF-8 is still rejected. XML
|
|
52
|
+
documents (`Makiri::XML`) are unchanged and still reject NUL everywhere, since
|
|
53
|
+
XML 1.0 has no legal U+0000 character.
|
|
54
|
+
|
|
3
55
|
## [0.5.1] - 2026-06-22
|
|
4
56
|
|
|
5
57
|
### 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,20 @@ 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
|
+
# The 10 CBMC proofs are independent processes with a lopsided cost
|
|
294
|
+
# distribution (a few ~1-2min solves dominate, the rest are trivial), so run
|
|
295
|
+
# them in parallel: wall-clock drops toward the single slowest harness with no
|
|
296
|
+
# loss of coverage. Cap -j at the CPU count (Etc.nprocessors; the runner has
|
|
297
|
+
# RAM for that many concurrent core-set solves). smoke/selftest are quick and
|
|
298
|
+
# come first as ordered goals.
|
|
299
|
+
require "etc"
|
|
300
|
+
jobs = Integer(ENV.fetch("VERIFY_JOBS", Etc.nprocessors))
|
|
301
|
+
sh "make", "-C", "verify", "-j#{jobs}", "smoke", "selftest", "cbmc"
|
|
302
|
+
end
|
|
303
|
+
|
|
290
304
|
desc "Run the performance benchmark (Makiri vs Nokogiri reference)"
|
|
291
305
|
task bench: :compile do
|
|
292
306
|
# Run outside the bundle so the bench-only gems (nokogiri, benchmark-ips)
|
|
@@ -303,13 +317,6 @@ task "bench:xml" => :compile do
|
|
|
303
317
|
end
|
|
304
318
|
end
|
|
305
319
|
|
|
306
|
-
desc "Run the CSS selector-query benchmark on a note.com-style SPA page (set BENCH_CARDS)"
|
|
307
|
-
task "bench:css" => :compile do
|
|
308
|
-
Bundler.with_unbundled_env do
|
|
309
|
-
sh "#{FileUtils::RUBY} -Ilib bench/bench_css_query.rb"
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
|
|
313
320
|
namespace :conformance do
|
|
314
321
|
desc "WHATWG HTML5 parsing conformance: run html5lib-tests through Makiri"
|
|
315
322
|
task html5: :compile do
|
data/lib/makiri/3.2/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/3.3/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/3.4/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/4.0/makiri.so
CHANGED
|
Binary file
|
data/lib/makiri/attr.rb
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Makiri
|
|
4
4
|
# An attribute node. Most of the API lives in C (#name, #value).
|
|
5
|
-
#
|
|
6
|
-
# An attribute knows its owning element through the document's lazily-built
|
|
7
|
-
# attr->owner index, exposed as both #parent and #element.
|
|
8
5
|
class Attr < Node
|
|
9
6
|
# The element this attribute belongs to, or nil if detached. Defined as a
|
|
10
7
|
# method (not an alias) so it resolves #parent dynamically on the per-kind
|
data/lib/makiri/comment.rb
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Makiri
|
|
4
4
|
class Comment < Node
|
|
5
|
-
# Create a detached comment owned by +document+ (Nokogiri-style constructor
|
|
6
|
-
# the document comes FIRST for Comment / CDATASection / ProcessingInstruction, unlike
|
|
7
|
-
# Element / Text). Delegates to {Document#create_comment}.
|
|
5
|
+
# Create a detached comment owned by +document+ (Nokogiri-style constructor)
|
|
8
6
|
#
|
|
9
7
|
# @param document [Makiri::Document]
|
|
10
8
|
# @param content [String]
|
|
@@ -3,18 +3,7 @@
|
|
|
3
3
|
module Makiri
|
|
4
4
|
# Nokogiri-compatible class-name aliases.
|
|
5
5
|
#
|
|
6
|
-
# Makiri's canonical node-class names are the WHATWG DOM interface names
|
|
7
|
-
# (Element, Attr, Text, Comment, CDATASection, ProcessingInstruction,
|
|
8
|
-
# DocumentType, Document, DocumentFragment). Two of those differ from the
|
|
9
|
-
# libxml2/Nokogiri spelling; we expose the Nokogiri names as aliases so a
|
|
10
|
-
# snippet ported from Nokogiri (or an is_a?/case check) resolves unchanged:
|
|
11
|
-
#
|
|
12
|
-
# CDATASection <- CDATA (Nokogiri::XML::CDATA)
|
|
13
|
-
# DocumentType <- DTD (Nokogiri::XML::DTD)
|
|
14
|
-
#
|
|
15
|
-
# An alias is the same class object, so #is_a? works under either name; only
|
|
16
|
-
# #class.name (and inspect) report the canonical DOM name. Defined at all three
|
|
17
|
-
# levels (the abstract base and the HTML::/XML:: leaves).
|
|
6
|
+
# Makiri's canonical node-class names are the WHATWG DOM interface names.
|
|
18
7
|
CDATA = CDATASection
|
|
19
8
|
DTD = DocumentType
|
|
20
9
|
|
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,
|
|
@@ -96,22 +96,13 @@ ignore_paths:
|
|
|
96
96
|
- path: ext/makiri/glue/ruby_node_set.c
|
|
97
97
|
rule: node_raw_boundary
|
|
98
98
|
reason: NodeSet.new stores representation-opaque pointers; mkr_node_raw takes a seed node's pointer only after same-document validation guarantees its kind matches the set.
|
|
99
|
-
|
|
100
|
-
# --- parser-TU reader discipline (raw_scan_call / raw_cursor_member) ---
|
|
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."
|
|
99
|
+
# verified_text_forge in fuzz/*.c
|
|
114
100
|
- path: ext/makiri/fuzz/xpath_fuzz.c
|
|
115
101
|
rule: verified_text_forge
|
|
116
|
-
max: 1
|
|
117
102
|
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)."
|
|
103
|
+
- path: ext/makiri/fuzz/xml_xpath_fuzz.c
|
|
104
|
+
rule: verified_text_forge
|
|
105
|
+
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."
|
|
106
|
+
|
|
107
|
+
# --- parser-TU reader discipline (raw_scan_call / raw_cursor_member) ---
|
|
108
|
+
allowlist: []
|
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_selftest.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
|