makiri 0.3.0-aarch64-linux → 0.4.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/conformance.yml +22 -0
- data/.github/workflows/libfuzzer.yml +83 -0
- data/.github/workflows/security.yml +88 -3
- data/.github/workflows/valgrind.yml +135 -0
- data/CHANGELOG.md +60 -2
- data/README.md +81 -77
- data/Rakefile +194 -3
- 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/cdata_section.rb +1 -3
- data/lib/makiri/comment.rb +1 -3
- data/lib/makiri/document.rb +8 -0
- data/lib/makiri/element.rb +1 -3
- data/lib/makiri/processing_instruction.rb +1 -3
- data/lib/makiri/text.rb +1 -3
- data/lib/makiri/version.rb +1 -1
- data/lib/makiri/xml/builder.rb +263 -0
- data/lib/makiri/xml/node_methods.rb +47 -0
- data/lib/makiri.rb +1 -0
- data/script/check_alloc_failures.rb +266 -0
- data/script/check_c_safety.rb +45 -2
- data/script/check_c_safety_allowlist.yml +19 -0
- data/script/check_leaks.rb +64 -0
- data/script/leaks_harness.rb +64 -0
- metadata +7 -1
|
@@ -93,3 +93,22 @@ ignore_paths:
|
|
|
93
93
|
- path: ext/makiri/glue/ruby_node_set.c
|
|
94
94
|
rule: node_raw_boundary
|
|
95
95
|
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.
|
|
96
|
+
|
|
97
|
+
# --- parser-TU reader discipline (raw_scan_call / raw_cursor_member) ---
|
|
98
|
+
allowlist:
|
|
99
|
+
- path: ext/makiri/xpath/mkr_xpath_funcs_body.h
|
|
100
|
+
rule: raw_scan_call
|
|
101
|
+
max: 3
|
|
102
|
+
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."
|
|
103
|
+
|
|
104
|
+
# --- verified_text_forge: the two Ruby-free test entry points. Neither can use
|
|
105
|
+
# mkr_verified_text_from_view (bridge = Ruby boundary), so each mints the
|
|
106
|
+
# token itself and must supply the contract by construction. ---
|
|
107
|
+
- path: ext/makiri/xpath/mkr_xpath_xml_selftest.c
|
|
108
|
+
rule: verified_text_forge
|
|
109
|
+
max: 2
|
|
110
|
+
reason: "Selftest expressions are compile-time string literals: NUL-terminated, NUL-free, valid UTF-8 by construction."
|
|
111
|
+
- path: ext/makiri/fuzz/xpath_fuzz.c
|
|
112
|
+
rule: verified_text_forge
|
|
113
|
+
max: 1
|
|
114
|
+
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)."
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Malloc-leak gate for the C extension (macOS only; run via `rake leaks`).
|
|
4
|
+
#
|
|
5
|
+
# ASan runs everywhere with detect_leaks=0 (Ruby and Lexbor are uninstrumented,
|
|
6
|
+
# so LeakSanitizer drowns in their noise) - which means plain leaks were never
|
|
7
|
+
# machine-checked. This gate fills that hole with macOS's `leaks` tool: it runs
|
|
8
|
+
# script/leaks_harness.rb (the public surface in a loop, INCLUDING rescued
|
|
9
|
+
# failure paths) under MallocStackLogging, then scans the leak report for
|
|
10
|
+
# allocation stacks that (a) pass through makiri.bundle and (b) repeat in
|
|
11
|
+
# proportion to the loop count - i.e. a leak per call, which is what a missing
|
|
12
|
+
# free on some path looks like. One-time Init allocations and Ruby's own
|
|
13
|
+
# at-exit/unscannable noise stay at 1-2 instances and are ignored.
|
|
14
|
+
#
|
|
15
|
+
# Found (and since fixed) by this harness: the transient fragment document
|
|
16
|
+
# leaked by every inner_html=/outer_html=, and the partially-built step leaked
|
|
17
|
+
# by every failing XPath parse.
|
|
18
|
+
#
|
|
19
|
+
# ruby script/check_leaks.rb # threshold = ITERATIONS / 4
|
|
20
|
+
# LEAKS_ITERATIONS=200 rake leaks # more iterations, sharper signal
|
|
21
|
+
|
|
22
|
+
require "rbconfig"
|
|
23
|
+
require "tempfile"
|
|
24
|
+
|
|
25
|
+
abort "check_leaks: the `leaks` tool is macOS-only" unless RUBY_PLATFORM.include?("darwin")
|
|
26
|
+
|
|
27
|
+
iterations = Integer(ENV.fetch("LEAKS_ITERATIONS", "120"))
|
|
28
|
+
threshold = [iterations / 4, 10].max
|
|
29
|
+
harness = File.expand_path("leaks_harness.rb", __dir__)
|
|
30
|
+
lib = File.expand_path("../lib", __dir__)
|
|
31
|
+
|
|
32
|
+
out = Tempfile.create("makiri-leaks")
|
|
33
|
+
ok = system({ "MallocStackLogging" => "1", "LEAKS_ITERATIONS" => iterations.to_s },
|
|
34
|
+
"leaks", "--atExit", "--",
|
|
35
|
+
RbConfig.ruby, "-I#{lib}", harness,
|
|
36
|
+
out: out.path, err: out.path)
|
|
37
|
+
report = File.read(out.path)
|
|
38
|
+
# `leaks` exits non-zero whenever ANY leak exists (Ruby itself always reports
|
|
39
|
+
# some at-exit noise), so the exit status is not the verdict - the scan below
|
|
40
|
+
# is. But the harness itself must have completed.
|
|
41
|
+
abort "check_leaks: harness did not complete:\n#{report[-2000..]}" unless report.include?("leaks harness done")
|
|
42
|
+
abort "check_leaks: no leak report produced (leaks tool failed?)" unless report.include?("STACK OF") || ok
|
|
43
|
+
|
|
44
|
+
offenders = report.split(/\n(?=STACK OF )/).filter_map do |stanza|
|
|
45
|
+
next unless stanza.include?("makiri.bundle")
|
|
46
|
+
|
|
47
|
+
instances = stanza[/STACK OF (\d+) INSTANCES?/, 1].to_i
|
|
48
|
+
next if instances < threshold
|
|
49
|
+
|
|
50
|
+
frames = stanza.lines.grep(/makiri\.bundle/).first(4).map(&:strip)
|
|
51
|
+
[instances, frames]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if offenders.empty?
|
|
55
|
+
puts "check_leaks: OK - no repeated (>= #{threshold}x) leak stacks through makiri.bundle " \
|
|
56
|
+
"(#{iterations} iterations)"
|
|
57
|
+
else
|
|
58
|
+
puts "check_leaks: FAILED - #{offenders.size} repeated leak stack(s) through makiri.bundle:"
|
|
59
|
+
offenders.each do |instances, frames|
|
|
60
|
+
puts " #{instances}x:"
|
|
61
|
+
frames.each { |f| puts " #{f}" }
|
|
62
|
+
end
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Exercise script for the malloc-leak gate (script/check_leaks.rb runs this
|
|
4
|
+
# under macOS `leaks --atExit`). It loops the public surface - parsing, queries,
|
|
5
|
+
# serialization, mutation, fragments, the Builder, XPathContext - and, crucially,
|
|
6
|
+
# RESCUED FAILURE paths (raises that skip cleanup are where leaks hide; both
|
|
7
|
+
# leaks this gate was built on lived there or in a transient-document path).
|
|
8
|
+
#
|
|
9
|
+
# A per-call leak shows up as a leak stack with ~ITERATIONS instances, which the
|
|
10
|
+
# driver flags; one-time/Init allocations stay at 1-2 instances and pass.
|
|
11
|
+
|
|
12
|
+
require "makiri"
|
|
13
|
+
|
|
14
|
+
ITERATIONS = Integer(ENV.fetch("LEAKS_ITERATIONS", "120"))
|
|
15
|
+
|
|
16
|
+
HTML = "<div id=m class='a b'><ul><li class=item>x</li><li>y<svg><path/></svg></li></ul><p>t&</p></div>"
|
|
17
|
+
XML = %(<r xmlns:p="urn:p" xmlns="urn:d"><a id="1">t</a><p:b/><!--c--><![CDATA[z]]></r>)
|
|
18
|
+
|
|
19
|
+
handler = Class.new { def my_fn(nodes) = nodes.length.to_s }.new
|
|
20
|
+
|
|
21
|
+
ITERATIONS.times do |i|
|
|
22
|
+
# --- HTML: parse / query / serialize / mutate / fragments ---
|
|
23
|
+
d = Makiri::HTML(HTML)
|
|
24
|
+
d.css("li.item"); d.at_css("#m"); d.at_css("li").matches?("li")
|
|
25
|
+
begin d.css("li[") rescue Makiri::CSS::SyntaxError; end # selector syntax error (engine reset path)
|
|
26
|
+
d.xpath("//li"); d.at_xpath("//p"); d.xpath("count(//li)")
|
|
27
|
+
begin d.xpath("//li[") rescue Makiri::XPath::SyntaxError; end # parse failure (partial-AST/step cleanup)
|
|
28
|
+
begin d.xpath("//li", handler) rescue nil; end
|
|
29
|
+
d.xpath("//*[local-name()='path']")
|
|
30
|
+
d.to_html; d.at_css("ul").inner_html; d.to_html(pretty: true); d.text
|
|
31
|
+
e = d.at_css("li"); e["k#{i}"] = "v"; e.name = "li2"; e.content = "c"
|
|
32
|
+
e.set_attribute_ns("urn:x", "x:y", "1"); e.remove_attribute_ns("urn:x", "y")
|
|
33
|
+
d.at_css("ul").inner_html = "<li>new</li>" # transient fragment document path
|
|
34
|
+
frag = d.fragment("<b>f</b>"); d.at_css("p") << frag
|
|
35
|
+
Makiri::DocumentFragment.parse("<tr><td>1</td></tr>", context: "tbody")
|
|
36
|
+
d.at_css("p").clone_node(true); d.dup
|
|
37
|
+
begin d.fragment("x", context: "no-such-tag") rescue ArgumentError; end
|
|
38
|
+
|
|
39
|
+
# --- XML: parse (ok + failures) / query / serialize / mutate / Builder ---
|
|
40
|
+
x = Makiri::XML(XML)
|
|
41
|
+
begin Makiri::XML("<r>\xC3</r>".b) rescue Makiri::XML::SyntaxError; end
|
|
42
|
+
begin Makiri::XML("<r><a></r>") rescue Makiri::XML::SyntaxError; end
|
|
43
|
+
begin Makiri::XML("<r/>", max_bytes: 1) rescue Makiri::XML::LimitExceeded; end
|
|
44
|
+
begin Makiri::XML(%(<?xml version="1.1"?><r/>)) rescue Makiri::XML::SyntaxError; end
|
|
45
|
+
x.xpath("//d:a", "d" => "urn:d"); x.at_xpath("//p:b", "p" => "urn:p")
|
|
46
|
+
begin x.xpath("//unbound:a") rescue Makiri::Error; end
|
|
47
|
+
x.css("a"); x.at_css("p|b", "p" => "urn:p")
|
|
48
|
+
begin x.css("a[") rescue Makiri::CSS::SyntaxError; end
|
|
49
|
+
x.to_xml; x.to_xml(pretty: true); x.root.canonicalize
|
|
50
|
+
el = x.create_element("n", "t", "k" => "v"); x.root.add_child(el)
|
|
51
|
+
begin x.root.add_child(x.create_element("zz:q")) rescue Makiri::Error; end
|
|
52
|
+
x.root.children.first.replace(x.create_element("rep")); x.fragment("<f1/><f2/>")
|
|
53
|
+
Makiri::XML::Builder.new { |b| b.root("xmlns:d" => "urn:d") { b.item("a"); b["d"].q } }.to_xml
|
|
54
|
+
|
|
55
|
+
# --- XPathContext: AST cache / registrations / failing evaluate ---
|
|
56
|
+
ctx = Makiri::XPathContext.new(x)
|
|
57
|
+
ctx.register_namespace("d", "urn:d"); ctx.register_variable("v", "1")
|
|
58
|
+
ctx.evaluate("//d:a[@id=$v]"); ctx.evaluate("//d:a[@id=$v]")
|
|
59
|
+
begin ctx.evaluate("//(") rescue Makiri::XPath::SyntaxError; end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
GC.start
|
|
63
|
+
GC.start
|
|
64
|
+
puts "leaks harness done (#{ITERATIONS} iterations)"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: makiri
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- takahashim
|
|
@@ -64,8 +64,10 @@ extra_rdoc_files: []
|
|
|
64
64
|
files:
|
|
65
65
|
- ".github/workflows/ci.yml"
|
|
66
66
|
- ".github/workflows/conformance.yml"
|
|
67
|
+
- ".github/workflows/libfuzzer.yml"
|
|
67
68
|
- ".github/workflows/release.yml"
|
|
68
69
|
- ".github/workflows/security.yml"
|
|
70
|
+
- ".github/workflows/valgrind.yml"
|
|
69
71
|
- ".gitmodules"
|
|
70
72
|
- CHANGELOG.md
|
|
71
73
|
- LICENSE
|
|
@@ -95,13 +97,17 @@ files:
|
|
|
95
97
|
- lib/makiri/text.rb
|
|
96
98
|
- lib/makiri/version.rb
|
|
97
99
|
- lib/makiri/xml.rb
|
|
100
|
+
- lib/makiri/xml/builder.rb
|
|
98
101
|
- lib/makiri/xml/document.rb
|
|
99
102
|
- lib/makiri/xml/node_methods.rb
|
|
100
103
|
- lib/makiri/xpath.rb
|
|
101
104
|
- lib/makiri/xpath_context.rb
|
|
102
105
|
- script/build_native_gem.rb
|
|
106
|
+
- script/check_alloc_failures.rb
|
|
103
107
|
- script/check_c_safety.rb
|
|
104
108
|
- script/check_c_safety_allowlist.yml
|
|
109
|
+
- script/check_leaks.rb
|
|
110
|
+
- script/leaks_harness.rb
|
|
105
111
|
- sig/makiri.rbs
|
|
106
112
|
homepage: https://github.com/takahashim/makiri
|
|
107
113
|
licenses:
|