makiri 0.6.0-aarch64-linux → 0.8.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: 6c0a020c0e52029940d64a91b53ae459d08b1d53e3106c3b312b6c86601bc29a
4
- data.tar.gz: 2d5bf5107d4de3892b70684aaad11b67f0f7eec74496e01132f79703db87262e
3
+ metadata.gz: 04cb87cd8a88d6c9ba3906801a76ddf05c81ccd8493198696a79df0d2c7a60ee
4
+ data.tar.gz: d00b6a2afd7c3b59bfb8b1bcd01bd453339af3218d481eaa1fff4fd70d20b305
5
5
  SHA512:
6
- metadata.gz: 6e0eda64911e48477808cf4b033b8f24fdbcb978558e27db9ea570bcd4364eb45e81e0a39b0c0e3ebe357aff622fb479e0c230b6c218f4f23ab760d3cd163cc8
7
- data.tar.gz: 96f2da4e924ade762e57887c128bd70583330917a18f09abafd2721c2fdc61f647e5d1eb64544b8af753182c2e83d320b9dd2e02dcd384e3187ac27b3b27a352
6
+ metadata.gz: 5d7e1303bda0a53db24ddeeec938f69449b2a28df8393cf2fda03942f3d35a92b9dba31a37c7247070c934f864436a53488cd1454f26af35400d428b0bc331a5
7
+ data.tar.gz: d164a159fecf71c9c8c24f588aeae7ba4ddebe2bb6c30d49de600c89ce871d91e2b8e0caa584f79a62d6e42289ffde60944d3c5c4630d4bddb89aa17191d8851
@@ -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, os: ubuntu-latest }
67
- - { platform: aarch64-linux, os: ubuntu-24.04-arm }
68
- - { platform: arm64-darwin, os: macos-14 }
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
- if [ "$(uname)" = "Darwin" ]; then
101
- if otool -L "$bin" | grep -i 'libruby'; then
102
- echo "::error::$bin hard-links libruby; precompiled gem would not load on other Ruby installs"; exit 1
103
- fi
104
- else
105
- if readelf -d "$bin" 2>/dev/null | grep -iE 'NEEDED.*libruby'; then
106
- echo "::error::$bin has a libruby NEEDED entry; precompiled gem may not load on a static Ruby"; exit 1
107
- fi
108
- fi
109
- echo "OK: $bin has no libruby dependency"
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'
@@ -24,11 +24,16 @@ jobs:
24
24
  # (intra-arena overflows). Runs on Linux because Valgrind is x86_64/amd64
25
25
  # Linux only.
26
26
  valgrind-memcheck:
27
- name: Valgrind memcheck (Ruby ${{ matrix.ruby }})
27
+ name: Valgrind memcheck (Ruby ${{ matrix.ruby }}, shard ${{ matrix.shard }}/4)
28
28
  runs-on: ubuntu-latest
29
29
  timeout-minutes: 360
30
30
  env:
31
31
  BUNDLE_WITH: valgrind
32
+ # Shard the suite across the matrix below: 4 parallel jobs each memcheck a
33
+ # quarter of the spec files, so the whole suite finishes in ~1/4 the
34
+ # wall-clock with no loss of coverage (each shard is balanced by file size).
35
+ VALGRIND_SHARDS: "4"
36
+ VALGRIND_SHARD_INDEX: ${{ matrix.shard }}
32
37
  # These heavy jobs verify memory discipline (uninit values, intra-arena
33
38
  # overflows, use-after-move), not the property space - so the full
34
39
  # 300-iteration PBT sweep (already run by the normal CI matrix) is
@@ -37,10 +42,16 @@ jobs:
37
42
  # keeping the run tractable.
38
43
  PBT_COUNT: "15"
39
44
  CSS_PBT_COUNT: "15"
45
+ # --track-origins roughly doubles memcheck's runtime and only adds an origin
46
+ # backtrace to a finding (the finding itself is still reported without it).
47
+ # Drop it on the frequent post-merge push gate to halve its runtime; keep it
48
+ # for the thorough nightly / manual runs, where the backtrace aids triage.
49
+ VALGRIND_TRACK_ORIGINS: ${{ github.event_name == 'push' && 'no' || 'yes' }}
40
50
  strategy:
41
51
  fail-fast: false
42
52
  matrix:
43
53
  ruby: ["3.4"]
54
+ shard: [0, 1, 2, 3] # keep in sync with VALGRIND_SHARDS above
44
55
 
45
56
  steps:
46
57
  - name: Checkout (with vendored Lexbor submodule)
data/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0] - 2026-07-12
4
+
5
+ ### Fixed
6
+
7
+ * `Document#import_node` (DOM `importNode` / `adoptNode`) now imports an HTML
8
+ element whose name is a valid DOM name but not a well-formed XML QName (e.g.
9
+ `":good:times:"`, `"x<"`, `"0:a"`) instead of raising. Such an element is not
10
+ XML-serializable (`#to_xml` raises). An element carrying a similarly lenient
11
+ attribute name is still rejected.
12
+
13
+ ## [0.7.0] - 2026-07-11
14
+
15
+ ### Added
16
+
17
+ * `Document#create_document_type(name, public_id = "", system_id = "")` on both
18
+ `Makiri::HTML::Document` and `Makiri::XML::Document` (DOM
19
+ `DOMImplementation.createDocumentType`): creates a detached `DocumentType`
20
+ node, to be placed before the document element (e.g. with
21
+ `root.add_previous_sibling(doctype)`). The name is case-preserving; an omitted
22
+ or empty public/system id is treated as absent; an invalid name (or a `"` in an
23
+ id) raises. Inserting a doctype is validated against the WHATWG rules — it may
24
+ only be a child of the document, must precede the document element, and a
25
+ document holds at most one — so a misplaced or duplicate doctype raises rather
26
+ than producing a malformed tree.
27
+
28
+ * The XML `DocumentType` is now part of the tree: it appears in
29
+ `Document#children` (before the root, like a browser DOM) and carries
30
+ `parent`/sibling links, for both a parsed `<!DOCTYPE>` and the factory above.
31
+ `Document#internal_subset` still returns it. XPath is unaffected — its 1.0 data
32
+ model excludes the doctype (`//node()` / `/node()` never surface it), as in
33
+ Nokogiri/libxml2. This supports building a document with
34
+ `DOMImplementation.createDocument(namespace, qualifiedName, doctype)`.
35
+
36
+ * `Makiri::XML::Document#create_loose_dom_element(qualified_name, prefix,
37
+ local_name, namespace_uri)`, an escape hatch for browser-DOM interop that
38
+ creates an element whose name follows WHATWG DOM element-name rules rather
39
+ than the stricter XML QName rules (e.g. `"f:o:o"`, `":foo"`, `"0:a"`). The
40
+ regular factories stay XML-strict. Such an element is intentionally not
41
+ XML-serializable: `#to_xml` and `#canonicalize` raise `Makiri::Error` if the
42
+ tree contains one. Renaming it to a valid XML name (`#name=`) returns it to
43
+ strict mode.
44
+
45
+ ### Fixed
46
+
47
+ * Replacing an XML node with a `DocumentFragment` (`node.replace(fragment)`) is
48
+ now all-or-nothing: if the replacement is rejected (e.g. it would leave the
49
+ document with two root elements), the target node and the fragment are left
50
+ untouched, instead of dropping the target and splicing in only part of the
51
+ fragment. Replacing the root element with a single-element fragment now works.
52
+
3
53
  ## [0.6.0] - 2026-07-05
4
54
 
5
55
  ### Changed
data/Rakefile CHANGED
@@ -88,7 +88,12 @@ begin
88
88
  "--error-limit=no",
89
89
  "--trace-children=yes", # spec processes may fork
90
90
  "--undef-value-errors=yes", # the point of this job (ruby_memcheck defaults to =no)
91
- "--track-origins=yes", # report where an uninitialised value came from
91
+ # Origin tracking (where an uninitialised value came from) roughly DOUBLES
92
+ # memcheck's run time. It is a diagnostic aid, not a detection capability -
93
+ # memcheck still reports the uninitialised USE without it - so the frequent
94
+ # post-merge push gate turns it off (VALGRIND_TRACK_ORIGINS=no) to run in
95
+ # ~half the time, while the nightly / manual runs keep it on for the backtrace.
96
+ "--track-origins=#{ENV.fetch('VALGRIND_TRACK_ORIGINS', 'yes')}",
92
97
  "--leak-check=no", # leaks are `rake leaks`' job, not this one
93
98
  ],
94
99
  )
@@ -96,7 +101,34 @@ begin
96
101
  namespace :spec do
97
102
  desc "Run the spec suite under Valgrind memcheck (ruby_memcheck; needs the " \
98
103
  ":valgrind bundler group and the valgrind binary)"
99
- RubyMemcheck::RSpec::RakeTask.new(valgrind: :compile)
104
+ RubyMemcheck::RSpec::RakeTask.new(valgrind: :compile) do |t|
105
+ # Let spec_helper skip :slow examples (fail-closed limit tests whose sheer
106
+ # volume dominates memcheck without adding memory-safety coverage).
107
+ ENV["VALGRIND"] = "1"
108
+
109
+ # Optional sharding for CI wall-clock: with VALGRIND_SHARDS=N (>1) the spec
110
+ # files are partitioned into N groups and this run does only group
111
+ # VALGRIND_SHARD_INDEX, so N parallel matrix jobs cover the whole suite in
112
+ # ~1/N the time WITHOUT dropping any coverage. Files are packed
113
+ # largest-first onto the currently-lightest shard (greedy LPT by file size,
114
+ # a proxy for runtime), so the slowest shard - which sets wall-clock - is
115
+ # balanced rather than left to name order. Deterministic, so every shard
116
+ # computes the same partition and takes its own index. Default N=1 = the
117
+ # whole suite (local `rake spec:valgrind`).
118
+ shards = Integer(ENV.fetch("VALGRIND_SHARDS", "1"))
119
+ if shards > 1
120
+ index = Integer(ENV.fetch("VALGRIND_SHARD_INDEX", "0"))
121
+ buckets = Array.new(shards) { { load: 0, files: [] } }
122
+ # sort by (size desc, name) so the packing is fully deterministic across
123
+ # shards regardless of Array#sort stability.
124
+ Dir["spec/**/*_spec.rb"].sort_by { |f| [-File.size(f), f] }.each do |f|
125
+ b = buckets.min_by { |x| x[:load] }
126
+ b[:files] << f
127
+ b[:load] += File.size(f)
128
+ end
129
+ t.pattern = buckets.fetch(index)[:files]
130
+ end
131
+ end
100
132
  end
101
133
  rescue LoadError
102
134
  # ruby_memcheck not installed (optional :valgrind group absent) - skip the task.
@@ -290,7 +322,15 @@ end
290
322
  desc "CBMC proofs over the Ruby/Lexbor-free carve-out (core + XML + XPath front; " \
291
323
  "needs cbmc; see docs/formal_verification.ja.md and verify/Makefile)"
292
324
  task :verify do
293
- sh "make -C verify smoke selftest cbmc"
325
+ # The 10 CBMC proofs are independent processes with a lopsided cost
326
+ # distribution (a few ~1-2min solves dominate, the rest are trivial), so run
327
+ # them in parallel: wall-clock drops toward the single slowest harness with no
328
+ # loss of coverage. Cap -j at the CPU count (Etc.nprocessors; the runner has
329
+ # RAM for that many concurrent core-set solves). smoke/selftest are quick and
330
+ # come first as ordered goals.
331
+ require "etc"
332
+ jobs = Integer(ENV.fetch("VERIFY_JOBS", Etc.nprocessors))
333
+ sh "make", "-C", "verify", "-j#{jobs}", "smoke", "selftest", "cbmc"
294
334
  end
295
335
 
296
336
  desc "Run the performance benchmark (Makiri vs Nokogiri reference)"
@@ -309,13 +349,6 @@ task "bench:xml" => :compile do
309
349
  end
310
350
  end
311
351
 
312
- desc "Run the CSS selector-query benchmark on a note.com-style SPA page (set BENCH_CARDS)"
313
- task "bench:css" => :compile do
314
- Bundler.with_unbundled_env do
315
- sh "#{FileUtils::RUBY} -Ilib bench/bench_css_query.rb"
316
- end
317
- end
318
-
319
352
  namespace :conformance do
320
353
  desc "WHATWG HTML5 parsing conformance: run html5lib-tests through Makiri"
321
354
  task html5: :compile do
Binary file
Binary file
Binary file
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
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Makiri
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -96,19 +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
- # --- 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. ---
99
+ # verified_text_forge in fuzz/*.c
107
100
  - path: ext/makiri/fuzz/xpath_fuzz.c
108
101
  rule: verified_text_forge
109
- max: 1
110
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)."
111
103
  - path: ext/makiri/fuzz/xml_xpath_fuzz.c
112
104
  rule: verified_text_forge
113
- max: 1
114
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/verify/Makefile CHANGED
@@ -36,7 +36,7 @@ LDLIBS ?= -lm
36
36
  INC := -I $(EXT) -I $(LXB) -I .
37
37
 
38
38
  CORE_SRC := $(EXT)/core/mkr_alloc.c $(EXT)/core/mkr_buf.c \
39
- $(EXT)/core/mkr_core.c $(EXT)/core/mkr_utf8.c
39
+ $(EXT)/core/mkr_core_selftest.c $(EXT)/core/mkr_utf8.c
40
40
  XML_SRC := $(EXT)/xml/mkr_xml_chars.c $(EXT)/xml/mkr_xml_index.c \
41
41
  $(EXT)/xml/mkr_xml_mutate.c $(EXT)/xml/mkr_xml_node.c \
42
42
  $(EXT)/xml/mkr_xml_tree.c
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.6.0
4
+ version: 0.8.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - takahashim