leptris 1.9.361.0-arm-linux → 1.9.361.1-arm-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: e44e85b78dc64588cec8feba58c1665317ad1b3097cc865b553e33d3ebf4304c
4
- data.tar.gz: 38a23d4e2004548427ebffee7e229a8a61f189c98939b0e1c4ced54defaa5cf4
3
+ metadata.gz: 62b22d9381c5d319e49dad015e2ff067de8b582f0572d1a950cde8bc7cbaf84f
4
+ data.tar.gz: baa7eaa60f9168c4e50a0514429731cbb7b958a71f5441a8f4bfa8f4a7c9a3cc
5
5
  SHA512:
6
- metadata.gz: fca50b93f64892deb3663334e33873ca42efc4872f7c8db109ed1ac40e63cfc050bdfb2464e96b5f057ab0631c352e78eeb29b50e4db6bba59674a0af737b90d
7
- data.tar.gz: 5bc185f99ad7db76f92534fb202a3bc6fcbf8066ba25e14992e0ab8a0adc64523eb90e336d5e69c8d435e2d0df474f3568d97897cc4c3aac061990cc2f65c089
6
+ metadata.gz: 259a2ec63050d803356e4208131c438559344714ad23012d3bb3f1b2b5a28e7eaa2f0c307fba8b761084aa500b959c9e903bf95068dcca185d0af5caffc48775
7
+ data.tar.gz: e004e9f4504604bad292f098eab5e111ba80d50ebfb08d4627f008b5a4aa32f5aff93b75165145fb4a745455ef924dbf894cf756a55ff4eda08308362dfc249d
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.359"
11
+ LIBLEPTRIS_VERSION = "1.9.361"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -292,13 +292,14 @@ task :compile do
292
292
  Dir.chdir(ext_dir) do
293
293
  sh "ruby extconf.rb"
294
294
  if RUBY_PLATFORM =~ /darwin/
295
- # Link the bundle ourselves: setup-ruby's custom rubies
296
- # make mkmf link libruby by absolute runner path no matter
297
- # which RbConfig entries are cleared, and any libruby
298
- # LC_LOAD_DYLIB is unresolvable on user machines. Compile
299
- # via the Makefile, link with pure -undefined
300
- # dynamic_lookup (Ruby symbols resolve from the loading
301
- # interpreter) and a conservative deployment target.
295
+ # Link the bundle ourselves: mkmf snapshots LIBRUBYARG from
296
+ # RbConfig at require time, so setup-ruby's shared-libruby
297
+ # rubies record their absolute hostedtoolcache path in the
298
+ # bundle (shipped broken through 1.9.36x, #1739) and no
299
+ # post-require clearing can stop it. Compile via the
300
+ # Makefile, link with pure -undefined dynamic_lookup (Ruby
301
+ # symbols resolve from the loading interpreter) and a
302
+ # conservative deployment target.
302
303
  sh "make native.o"
303
304
  sh "cc -dynamic -bundle -undefined dynamic_lookup " \
304
305
  "-mmacosx-version-min=#{ENV['MACOSX_DEPLOYMENT_TARGET'] || '11.0'} " \
@@ -312,10 +313,11 @@ task :compile do
312
313
  # their symbols via -rdynamic, like any extension .so).
313
314
  sh "make native.o"
314
315
  sh "#{RbConfig::CONFIG['CC']} -shared -fPIC -o native.so native.o"
315
- # Guard the contract in the build log.
316
- sh "if strings native.so | grep -q libruby; then " \
317
- "echo 'ERROR: native.so links libruby (#207)'; exit 1; fi"
318
316
  end
317
+ # Guard the linkage contract at the build chokepoint (#1739):
318
+ # no libruby reference, allowlisted load commands on darwin.
319
+ sh "#{RbConfig.ruby} ../../check_bundle_linkage.rb " \
320
+ "#{RUBY_PLATFORM =~ /darwin/ ? 'native.bundle' : 'native.so'}"
319
321
  end
320
322
  bundle = Dir.glob("#{ext_dir}/native.{bundle,so,dll}").first
321
323
  if bundle.nil?
@@ -337,6 +339,18 @@ task :compile do
337
339
  cp(bundle, "#{dest}.new")
338
340
  mv("#{dest}.new", dest)
339
341
  sign_darwin_binaries(dest)
342
+ # Per-minor twin (ruby#476): the loader prefers
343
+ # native_<minor>.{bundle,so}; dev builds run same-minor, so
344
+ # the copy is byte-identical and keeps `rake compile` +
345
+ # `rake spec` working without a full release matrix.
346
+ unless Gem.win_platform?
347
+ minor = RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')
348
+ ext = RUBY_PLATFORM.include?('darwin') ? '.bundle' : '.so'
349
+ twin = "lib/leptris/xml/native_#{minor}#{ext}"
350
+ cp(dest, "#{twin}.new")
351
+ mv("#{twin}.new", twin)
352
+ sign_darwin_binaries(twin)
353
+ end
340
354
  puts "Vendored native layer (#{File.basename(bundle)}) into lib/leptris/xml/"
341
355
  end
342
356
  end
@@ -412,11 +426,19 @@ namespace :audit do
412
426
  # leptris_version() equals the gem's engine pin (a half-bumped
413
427
  # lockstep state can no longer package). Negative proof lives
414
428
  # in spec/audit_codesign_spec.rb.
415
- desc "Fail on unsigned/tampered macOS artifacts or engine-version drift"
429
+ desc "Fail on unsigned/tampered macOS artifacts, engine-version drift, or libruby-linked native layers"
416
430
  task :artifacts do
431
+ # Linkage contract over EVERY shipped native layer, per-minor
432
+ # twins included (#1739) — every platform: the 1.9.36x trains
433
+ # packaged runner-absolute libruby loads in native_3_2/3_4
434
+ # artifacts that audit:symbols/audit:artifacts never opened.
435
+ bundles = Dir.glob("lib/leptris/xml/native*.{bundle,so}")
436
+ bundles.each do |b|
437
+ sh "#{RbConfig.ruby} ext/check_bundle_linkage.rb #{b}"
438
+ end
417
439
  unless RUBY_PLATFORM =~ /darwin/
418
- puts "audit:artifacts: skipped off-darwin (codesign gates " \
419
- "the macOS packaging legs)"
440
+ puts "audit:artifacts: #{bundles.size} native layers linkage-clean; " \
441
+ "codesign gates skip off-darwin"
420
442
  next
421
443
  end
422
444
  abort "audit:artifacts: codesign unavailable" \
@@ -439,8 +461,8 @@ namespace :audit do
439
461
  abort "audit:artifacts: engine-version drift — vendored " \
440
462
  "#{vendored.inspect} != pin #{pinned.inspect}"
441
463
  end
442
- puts "audit:artifacts: #{libs.size} macOS artifacts verify " \
443
- "strictly and engine-synced (#{pinned})"
464
+ puts "audit:artifacts: #{libs.size} macOS artifacts + #{bundles.size} native " \
465
+ "layers verify strictly, linkage-clean and engine-synced (#{pinned})"
444
466
  end
445
467
 
446
468
  # Packaging doctrine (#271): every BUILT gem carries the engine
@@ -563,14 +585,18 @@ platforms.each do |platform|
563
585
  spec.files += Dir.glob("vendor-src/**/*")
564
586
  spec.files += %w[ext/leptris/native/extconf.rb
565
587
  ext/leptris/native/native.c]
566
- if platform.include?("mingw")
567
- # #207/#227: per-Ruby-minor DLLs (PE must bind its Ruby).
568
- # DOT-FREE names — MRI derives the init symbol from the
569
- # basename cut at the first dot (native_3_3.so ->
570
- # Init_native_3_3; the dotted form made Ruby look for
571
- # "Init_native-3", which cannot exist).
572
- spec.files += Dir.glob("lib/leptris/xml/native_*.so")
573
- else
588
+ # #207/#227 + ruby#476: per-Ruby-minor bundles on EVERY
589
+ # platform (the ext's compiled-in RString layout changed in
590
+ # 3.2 — one CI-built bundle segfaults 3.1). DOT-FREE names —
591
+ # MRI derives the init symbol from the basename cut at the
592
+ # first dot (native_3_3.so -> Init_native_3_3; the dotted form
593
+ # made Ruby look for "Init_native-3", which cannot exist).
594
+ spec.files += Dir.glob("lib/leptris/xml/native_*.so")
595
+ spec.files += Dir.glob("lib/leptris/xml/native_*.bundle")
596
+ if !platform.include?("mingw")
597
+ # The unversioned bundle rides for toolchains that only run
598
+ # rake compile's single build (native_layer prefers the
599
+ # per-minor variant and never loads a wrong-ABI file).
574
600
  spec.files += Dir.glob("lib/leptris/xml/native.{bundle,so,dll}")
575
601
  end
576
602
  task = Gem::PackageTask.new(spec)
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Builds the native read layer bundle for the CURRENT Ruby and
4
+ # installs it under its minor-versioned name
5
+ # (lib/leptris/xml/native_<major>_<minor>.bundle on darwin, .so on
6
+ # Linux).
7
+ #
8
+ # ruby#476: the ext's compiled-in RString layout changed in Ruby
9
+ # 3.2 — a bundle built by a newer minor reads garbage pointers and
10
+ # lengths under 3.1 (the parse segfaulted inside the engine's
11
+ # correctly-bounded NEON text counter). One bundle per supported
12
+ # minor therefore ships in the darwin/Linux platform gems — the
13
+ # same contract the Windows gems have had since #207/#227 — and
14
+ # native_layer picks by RUBY_VERSION at require, falling back to
15
+ # the FFI surface when the variant for the running minor is
16
+ # absent. A WRONG-ABI load is never attempted.
17
+ #
18
+ # The name is DOT-FREE: MRI derives a C extension's init symbol
19
+ # from the require feature's basename cut at the first dot, so
20
+ # native_3_1.bundle resolves Init_native_3_1 (exported by
21
+ # native.c).
22
+
23
+ require "rbconfig"
24
+ require "fileutils"
25
+
26
+ root = File.expand_path("..", __dir__)
27
+ ext_dir = File.join(root, "ext", "leptris", "native")
28
+ minor = RUBY_VERSION[/\A\d+\.\d+/]
29
+ minor_us = minor.tr(".", "_")
30
+ ext = RUBY_PLATFORM.include?("darwin") ? ".bundle" : ".so"
31
+
32
+ Dir.chdir(ext_dir) do
33
+ # Force a clean compile on every invocation (stale objects from
34
+ # a failed earlier attempt ship silent wrong-ABI builds).
35
+ FileUtils.rm_f(Dir.glob("#{ext_dir}/native.{o,so,bundle}"))
36
+ system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
37
+ # Link WITHOUT libruby (#1739): mkmf's Makefile rule records the
38
+ # building ruby's libruby — for setup-ruby builds by absolute
39
+ # hostedtoolcache path — in every LC_LOAD_DYLIB / DT_NEEDED, which
40
+ # dlopen-fails on any other machine. Compile via the Makefile,
41
+ # link exactly like rake compile's native branch: pure
42
+ # dynamic_lookup, rb_* resolve from the loading interpreter.
43
+ system("make native.o") or abort "make native.o failed under #{RUBY_VERSION}"
44
+ cc = RbConfig::CONFIG["CC"]
45
+ link =
46
+ if ext == ".bundle"
47
+ "#{cc} -dynamic -bundle -undefined dynamic_lookup " \
48
+ "-mmacosx-version-min=#{ENV['MACOSX_DEPLOYMENT_TARGET'] || '11.0'} " \
49
+ "-o native.bundle native.o"
50
+ else
51
+ "#{cc} -shared -fPIC -o native.so native.o"
52
+ end
53
+ system(link) or abort "native link failed under #{RUBY_VERSION}: #{link}"
54
+ built = Dir.glob("native.{so,bundle}").first
55
+ abort "native bundle not produced under #{RUBY_VERSION}" unless built
56
+
57
+ # Linkage gate: no libruby reference, and (darwin) only
58
+ # allowlisted load commands — enforced HERE so a violated bundle
59
+ # never reaches packaging (the 1.9.36x trains shipped exactly
60
+ # this shape, #1739).
61
+ system(RbConfig.ruby, File.join(root, "ext", "check_bundle_linkage.rb"),
62
+ File.expand_path(built)) or
63
+ abort "linkage gate failed under #{RUBY_VERSION}"
64
+
65
+ # Functional gate: dlopen the built bundle and resolve the
66
+ # MINOR-SUFFIXED init symbol — only a real lookup proves
67
+ # require "leptris/xml/native_<minor>" will resolve.
68
+ begin
69
+ require "fiddle"
70
+ Fiddle.dlopen(File.expand_path(built))["Init_native_#{minor_us}"]
71
+ rescue LoadError => e
72
+ warn "fiddle unavailable under #{RUBY_VERSION} (#{e.message}) — skipping"
73
+ rescue Fiddle::DLError => e
74
+ abort "#{built} does not export Init_native_#{minor_us} — refusing to " \
75
+ "install a bundle that would fail at require (#{e.message})"
76
+ end
77
+
78
+ dest = File.join(root, "lib", "leptris", "xml", "native_#{minor_us}#{ext}")
79
+ FileUtils.cp(built, dest)
80
+ puts "Installed native layer for Ruby #{minor} -> #{dest}"
81
+ end
@@ -29,14 +29,17 @@ Dir.chdir(ext_dir) do
29
29
  # because make reused a stale native.obj from a failed earlier
30
30
  # attempt (the log shows "creating Makefile -> linking" with no
31
31
  # compile). Force a clean compile on every invocation.
32
- FileUtils.rm_f(Dir.glob("#{ext_dir}/native.{o,obj,so,dll}"))
33
- FileUtils.rm_f("#{ext_dir}/leptris/xml/native.so")
32
+ # The globs cover both spellings of the mkmf target: the
33
+ # unversioned name and the per-minor name extconf now creates
34
+ # the Makefile for (#476 source-install parity).
35
+ FileUtils.rm_f(Dir.glob("#{ext_dir}/native*.{o,obj,so,dll}"))
36
+ FileUtils.rm_f(Dir.glob("#{ext_dir}/leptris/xml/native*.so"))
34
37
  system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
35
38
  # mkmf's link binds the CURRENT Ruby's runtime DLL — exactly
36
39
  # what the versioned naming is for.
37
40
  success = system("make")
38
41
  abort "make failed under #{RUBY_VERSION}" unless success
39
- so = Dir.glob("native.{so,dll}").first
42
+ so = Dir.glob("native*.so").first
40
43
  abort "native bundle not produced under #{RUBY_VERSION}" unless so
41
44
  # Functional export gate: dlopen the built DLL and resolve the
42
45
  # MINOR-SUFFIXED init symbol. A PE DLL can carry the symbol in its
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Linkage contract for shipped native layers (#1739 / ruby#481): a
4
+ # Ruby extension resolves rb_* symbols from the LOADING interpreter,
5
+ # so the artifact must never link libruby — mkmf records the
6
+ # building ruby's absolute path (setup-ruby's hostedtoolcache
7
+ # location), which dlopen-fails on every other machine and silently
8
+ # degrades the whole install to FFI. On darwin the check is
9
+ # stricter: every load command must stay inside the loader
10
+ # allowlist. Windows PE DLLs are exempt — binding the build
11
+ # runtime is the documented contract there (#227).
12
+ #
13
+ # Runnable as a script over any number of artifacts (exit 1 on the
14
+ # first violated contract batch) or required for the pure checks in
15
+ # spec/check_bundle_linkage_spec.rb.
16
+ module CheckBundleLinkage
17
+ ALLOWED_PREFIXES = %w[@rpath/ @loader_path/ @executable_path/ /usr/lib/ /System/].freeze
18
+
19
+ # The LC_LOAD_DYLIB paths otool -L lists that fall outside the
20
+ # allowlist. +otool_output+ is the raw stdout of `otool -L <file>`;
21
+ # entries are the tab-indented "<install name> (compatibility
22
+ # version X, current version Y)" lines.
23
+ def self.load_command_violations(otool_output)
24
+ otool_output.lines.grep(/^\t.*\(compatibility version/)
25
+ .filter_map { |l| l.split.first }
26
+ .reject { |path| ALLOWED_PREFIXES.any? { |p| path.start_with?(p) } }
27
+ end
28
+
29
+ # The libruby sonames a strings(1) dump reveals — the ELF-side
30
+ # tripwire for DT_NEEDED entries and RUNPATH spellings alike.
31
+ def self.libruby_references(strings_output)
32
+ strings_output.scan(/libruby[\w.]*/).uniq
33
+ end
34
+ end
35
+
36
+ if $PROGRAM_NAME == __FILE__
37
+ require "shellwords"
38
+
39
+ status = 0
40
+ ARGV.each do |path|
41
+ next if path.end_with?(".dll")
42
+
43
+ bad = CheckBundleLinkage.libruby_references(`strings #{Shellwords.escape(path)} 2>/dev/null`)
44
+ unless bad.empty?
45
+ warn "ERROR: #{path} references #{bad.join(', ')} — Ruby symbols must " \
46
+ "resolve from the loading interpreter, never a linked libruby " \
47
+ "(#1739); link with -undefined dynamic_lookup / plain -shared"
48
+ status = 1
49
+ end
50
+ next unless RUBY_PLATFORM.include?("darwin")
51
+
52
+ viol = CheckBundleLinkage.load_command_violations(`otool -L #{Shellwords.escape(path)} 2>/dev/null`)
53
+ next if viol.empty?
54
+
55
+ warn "ERROR: #{path} carries load commands outside the loader " \
56
+ "allowlist (#{CheckBundleLinkage::ALLOWED_PREFIXES.join(' ')}): " \
57
+ "#{viol.join(', ')} (#1739)"
58
+ status = 1
59
+ end
60
+ exit status
61
+ end
@@ -28,6 +28,19 @@ unless %w[ruby truffleruby].include?(RUBY_ENGINE)
28
28
  "installing without the compiled native layer"
29
29
  exit 0
30
30
  end
31
+ # Never link libruby: a Ruby extension resolves rb_* symbols from
32
+ # the LOADING interpreter, and mkmf records the building ruby's
33
+ # libruby (for setup-ruby, an absolute hostedtoolcache path) into
34
+ # the bundle otherwise — dlopen-failing on every other machine
35
+ # (#1739). mkmf snapshots $LIBRUBYARG* from RbConfig at require
36
+ # time, so this MUST precede `require "mkmf"`; a later clearing is
37
+ # a silent no-op (exactly how the 1.9.36x trains shipped broken).
38
+ if RUBY_PLATFORM =~ /darwin/
39
+ %w[LIBRUBYARG LIBRUBYARG_SHARED LIBRUBYARG_STATIC].each do |key|
40
+ RbConfig::CONFIG[key] = ""
41
+ RbConfig::MAKEFILE_CONFIG[key] = ""
42
+ end
43
+ end
31
44
  require "mkmf"
32
45
 
33
46
  # A prebuilt libleptris in lib/ (dev checkout after rake compile,
@@ -106,18 +119,25 @@ if RUBY_PLATFORM =~ /darwin/
106
119
  [$LDFLAGS, $DLDFLAGS].each do |var|
107
120
  var << lookup unless var.include?("dynamic_lookup")
108
121
  end
109
- # setup-ruby builds carry the RUNNER's absolute libruby path in
110
- # LIBRUBYARG_SHARED — recorded as an LC_LOAD_DYLIB in the
111
- # bundle, unresolvable on user machines. A Ruby extension
112
- # resolves Ruby symbols from the loading interpreter; never
113
- # link libruby.
114
- # BOTH maps: mkmf's Makefile interpolates from MAKEFILE_CONFIG,
115
- # not CONFIG — clearing only CONFIG is a silent no-op (caught via
116
- # the build-log otool on the runner).
117
- RbConfig::CONFIG["LIBRUBYARG_SHARED"] = ""
118
- RbConfig::CONFIG["LIBRUBYARG_STATIC"] = ""
119
- RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_SHARED"] = ""
120
- RbConfig::MAKEFILE_CONFIG["LIBRUBYARG_STATIC"] = ""
122
+ # ruby#476: newer ld64 defaults BUNDLES to two-level namespace —
123
+ # with -undefined dynamic_lookup a two-level bind records the
124
+ # symbol as expected from the MAIN EXECUTABLE ("main-executable"),
125
+ # and interpreters that don't re-export every ruby symbol from
126
+ # their binary abort at first call ("dyld missing symbol called",
127
+ # hit at Init_native's first import). -flat_namespace is the
128
+ # classic C-extension pattern: dynamic_lookup symbols search all
129
+ # loaded images at call time and resolve from libruby.
130
+ flat = " -Wl,-flat_namespace"
131
+ [$LDFLAGS, $DLDFLAGS].each do |var|
132
+ var << flat unless var.include?("flat_namespace")
133
+ end
134
+ # Belt for the require-time snapshot: RbConfig was cleared BEFORE
135
+ # `require "mkmf"` (top of file), and the module globals mkmf
136
+ # captured then are zeroed here too — a Ruby extension resolves
137
+ # Ruby symbols from the loading interpreter; never link libruby.
138
+ $LIBRUBYARG = ""
139
+ $LIBRUBYARG_SHARED = ""
140
+ $LIBRUBYARG_STATIC = ""
121
141
  end
122
142
  if Gem.win_platform?
123
143
  # Windows names the artifact per Ruby minor, DOT-FREE (#227
@@ -137,4 +157,12 @@ if Gem.win_platform?
137
157
  win_def = " #{def_path.tr('/', '\\\\')}"
138
158
  $DLDFLAGS << win_def unless $DLDFLAGS.include?("native_exports.def")
139
159
  end
140
- create_makefile("leptris/xml/native")
160
+ # Name the artifact per Ruby minor EVERYWHERE (#476): the loader
161
+ # requires native_<minor> and deliberately has NO unversioned
162
+ # fallback (loading a bundle built by another minor is the 3.1
163
+ # wrong-ABI segfault class), so a source install must produce the
164
+ # per-minor name itself. mkmf derives Init_native_<minor> from the
165
+ # same basename — exported by native.c and pinned in the Windows
166
+ # .def above.
167
+ minor_us = RUBY_VERSION[/\A\d+\.\d+/].tr(".", "_")
168
+ create_makefile("leptris/xml/native_#{minor_us}")
@@ -157,6 +157,19 @@ static node_xpath_fn f_node_xpath;
157
157
  static elem_copy_fn f_elem_copy;
158
158
  static parse_str_fn f_parse_str;
159
159
  static parse_frag_fn f_parse_frag;
160
+ /* LeptrisParseOptions mirror (frozen layout since 1.9.0: flags,
161
+ * strict_mode, max_depth, recover — the FFI ParseOptionsStruct
162
+ * shape). Only recover is ever set: the recover-default parse
163
+ * (ruby#465) routes through the ex face on the C side. */
164
+ struct leptris_parse_opts_mirror {
165
+ int flags;
166
+ int strict_mode;
167
+ int max_depth;
168
+ int recover;
169
+ };
170
+ typedef void *(*parse_str_ex_fn)(const char *, size_t,
171
+ struct leptris_parse_opts_mirror *, void *);
172
+ static parse_str_ex_fn f_parse_str_ex;
160
173
  static last_err_fn f_last_err;
161
174
  static status_str_fn f_status_str;
162
175
  static set_root_fn f_set_root;
@@ -173,6 +186,13 @@ static ID id_iv_nn_content, id_iv_nn_content_ver;
173
186
  static ID id_iv_nn_attrs, id_iv_nn_attrs_ver;
174
187
  static ID id_child_klasses;
175
188
  static VALUE c_use_after_free_error;
189
+ /* ruby#476: rb_cObject / rb_e* are DATA symbols — under the newer
190
+ * macOS linker's chained fixups a -undefined dynamic_lookup DATA
191
+ * import binds at LOAD and fails ("Symbol not found: _rb_cObject")
192
+ * on interpreters whose main executable does not re-export them
193
+ * (mise builds). Function imports bind lazily and always resolve,
194
+ * so these four resolve at INIT via rb_path2class into statics. */
195
+ static VALUE c_object, c_arg_error, c_type_error, c_runtime_error;
176
196
 
177
197
  static VALUE native_cache_of(VALUE document);
178
198
  static void resolve_binding_classes(void);
@@ -236,7 +256,7 @@ static void resolve_symbols(const char *lib_path)
236
256
  {
237
257
  void *h = lib_open(lib_path);
238
258
  if (!h)
239
- rb_raise(rb_eRuntimeError, "cannot load library %s", lib_path);
259
+ rb_raise(c_runtime_error, "cannot load library %s", lib_path);
240
260
  f_elem_name = (elem_name_fn)lib_sym(h, "leptris_element_name");
241
261
  f_text_content = (text_content_fn)lib_sym(h, "leptris_text_node_get_content");
242
262
  f_attr = (attr_fn)lib_sym(h, "leptris_element_attribute");
@@ -304,6 +324,7 @@ static void resolve_symbols(const char *lib_path)
304
324
  f_node_xpath = (node_xpath_fn)lib_sym(h, "leptris_node_get_xpath");
305
325
  f_elem_copy = (elem_copy_fn)lib_sym(h, "leptris_element_copy");
306
326
  f_parse_str = (parse_str_fn)lib_sym(h, "leptris_parse_string");
327
+ f_parse_str_ex = (parse_str_ex_fn)lib_sym(h, "leptris_parse_string_ex");
307
328
  f_parse_frag = (parse_frag_fn)lib_sym(h, "leptris_parse_fragment");
308
329
  f_last_err = (last_err_fn)lib_sym(h, "leptris_last_error");
309
330
  f_status_str = (status_str_fn)lib_sym(h, "leptris_status_string");
@@ -326,7 +347,7 @@ static void resolve_symbols(const char *lib_path)
326
347
  !f_text_set_content || !f_node_unlink ||
327
348
  !f_node_traverse || !f_node_visit || !f_free_str ||
328
349
  !f_node_xpath || !f_elem_copy || !f_parse_str ||
329
- !f_parse_frag || !f_last_err || !f_status_str ||
350
+ !f_parse_frag || !f_parse_str_ex || !f_last_err || !f_status_str ||
330
351
  !f_doc_free || !f_sax_parse || !f_sax_create || !f_sax_feed ||
331
352
  !f_sax_pfree || !f_sax_set_flags ||
332
353
  !f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
@@ -335,7 +356,7 @@ static void resolve_symbols(const char *lib_path)
335
356
  !f_elem_ns_count || !f_elem_ns ||
336
357
  !f_attr_next || !f_attr_name || !f_attr_value ||
337
358
  !f_node_line || !f_node_offset)
338
- rb_raise(rb_eRuntimeError, "libleptris symbols missing");
359
+ rb_raise(c_runtime_error, "libleptris symbols missing");
339
360
  }
340
361
 
341
362
  static VALUE nn_name(VALUE self)
@@ -487,7 +508,7 @@ static VALUE nn_install_child_klasses(VALUE self, VALUE map)
487
508
  Check_Type(map, T_ARRAY);
488
509
  len = RARRAY_LEN(map);
489
510
  if (len > 5)
490
- rb_raise(rb_eArgError,
511
+ rb_raise(c_arg_error,
491
512
  "child klasses: Array of classes/nil indexed by "
492
513
  "node kind (element, text, comment, cdata, pi)");
493
514
  for (i = 0; i < len; i++) {
@@ -495,7 +516,7 @@ static VALUE nn_install_child_klasses(VALUE self, VALUE map)
495
516
  if (!NIL_P(k) &&
496
517
  !(RB_TYPE_P(k, T_CLASS) &&
497
518
  RTEST(rb_class_inherited_p(k, c_native_node))))
498
- rb_raise(rb_eTypeError,
519
+ rb_raise(c_type_error,
499
520
  "child klasses entries must be nil or a "
500
521
  "Leptris::XML::NativeNode subclass");
501
522
  }
@@ -685,7 +706,7 @@ static void *doc_ptr_of(VALUE document)
685
706
  {
686
707
  VALUE addr = rb_ivar_get(document, id_iv_c_address);
687
708
  if (NIL_P(addr))
688
- rb_raise(rb_eRuntimeError, "document has been freed");
709
+ rb_raise(c_runtime_error, "document has been freed");
689
710
  return (void *)(uintptr_t)NUM2ULL(addr);
690
711
  }
691
712
 
@@ -730,7 +751,7 @@ static VALUE nf_register_doc_klasses(VALUE self, VALUE document, VALUE map)
730
751
  Check_Type(map, T_ARRAY);
731
752
  len = RARRAY_LEN(map);
732
753
  if (len > 5)
733
- rb_raise(rb_eArgError,
754
+ rb_raise(c_arg_error,
734
755
  "wrapper klasses: Array of classes/nil indexed by "
735
756
  "node kind (element, text, comment, cdata, pi)");
736
757
  for (i = 0; i < len; i++) {
@@ -738,7 +759,7 @@ static VALUE nf_register_doc_klasses(VALUE self, VALUE document, VALUE map)
738
759
  if (!NIL_P(k) &&
739
760
  !(RB_TYPE_P(k, T_CLASS) &&
740
761
  RTEST(rb_class_inherited_p(k, c_native_node))))
741
- rb_raise(rb_eTypeError,
762
+ rb_raise(c_type_error,
742
763
  "wrapper klasses entries must be nil or a "
743
764
  "Leptris::XML::NativeNode subclass");
744
765
  }
@@ -1378,7 +1399,7 @@ static VALUE nf_insert_binding_child(VALUE self, VALUE document,
1378
1399
  case 2: st = f_insert_after(anchor, child); break;
1379
1400
  case 3: st = f_insert_before(anchor, child); break;
1380
1401
  default:
1381
- rb_raise(rb_eArgError, "invalid insert mode %d", m);
1402
+ rb_raise(c_arg_error, "invalid insert mode %d", m);
1382
1403
  return Qnil;
1383
1404
  }
1384
1405
  check_status_c(st);
@@ -1393,7 +1414,7 @@ static VALUE nn_create_element(VALUE klass, VALUE document, VALUE name)
1393
1414
  (void)klass;
1394
1415
  ptr = f_elem_create(doc_ptr_of(document), StringValueCStr(name));
1395
1416
  if (!ptr)
1396
- rb_raise(rb_eRuntimeError, "leptris_element_create failed");
1417
+ rb_raise(c_runtime_error, "leptris_element_create failed");
1397
1418
  return wrap_new(document, ptr);
1398
1419
  }
1399
1420
 
@@ -1403,7 +1424,7 @@ static VALUE nn_create_text(VALUE klass, VALUE document, VALUE content)
1403
1424
  (void)klass;
1404
1425
  ptr = f_text_create(doc_ptr_of(document), StringValueCStr(content));
1405
1426
  if (!ptr)
1406
- rb_raise(rb_eRuntimeError, "leptris_text_node_create failed");
1427
+ rb_raise(c_runtime_error, "leptris_text_node_create failed");
1407
1428
  return wrap_new(document, ptr);
1408
1429
  }
1409
1430
 
@@ -1416,7 +1437,7 @@ static VALUE nn_create_child(VALUE self, VALUE name)
1416
1437
  document_advance_version(n->document);
1417
1438
  ptr = f_create_child(n->ptr, StringValueCStr(name));
1418
1439
  if (!ptr)
1419
- rb_raise(rb_eRuntimeError, "leptris_element_create_child failed");
1440
+ rb_raise(c_runtime_error, "leptris_element_create_child failed");
1420
1441
  out = wrap_new(n->document, ptr);
1421
1442
  return out;
1422
1443
  }
@@ -1430,7 +1451,7 @@ static VALUE nn_append_child(VALUE self, VALUE child)
1430
1451
  document_advance_version(n->document);
1431
1452
  rc = f_append_child(n->ptr, c->ptr);
1432
1453
  if (rc != 0)
1433
- rb_raise(rb_eRuntimeError, "leptris_element_append_child failed (%d)", rc);
1454
+ rb_raise(c_runtime_error, "leptris_element_append_child failed (%d)", rc);
1434
1455
  return child;
1435
1456
  }
1436
1457
 
@@ -1443,7 +1464,7 @@ static VALUE nn_set_root(VALUE klass, VALUE document, VALUE element)
1443
1464
  document_advance_version(document);
1444
1465
  rc = f_set_root(doc_ptr_of(document), n->ptr);
1445
1466
  if (rc != 0)
1446
- rb_raise(rb_eRuntimeError, "leptris_document_set_root failed (%d)", rc);
1467
+ rb_raise(c_runtime_error, "leptris_document_set_root failed (%d)", rc);
1447
1468
  return element;
1448
1469
  }
1449
1470
 
@@ -2897,14 +2918,28 @@ static void check_status_c(int st)
2897
2918
  /* Document.parse default path in one dispatch (TODO.perf/35):
2898
2919
  * leptris_parse_string + wrapper + handle. Qnil = parse failure
2899
2920
  * (the caller raises with the same message shape). */
2900
- static VALUE nf_parse_binding_document(VALUE self, VALUE xml)
2921
+ static VALUE nf_parse_binding_document(int argc, VALUE *argv, VALUE self)
2901
2922
  {
2902
2923
  void *doc;
2924
+ VALUE xml, recover;
2903
2925
 
2904
2926
  (void)self;
2927
+ rb_scan_args(argc, argv, "11", &xml, &recover);
2905
2928
  resolve_binding_classes();
2906
- doc = f_parse_str(RSTRING_PTR(StringValue(xml)),
2907
- (size_t)RSTRING_LEN(StringValue(xml)), NULL);
2929
+ xml = StringValue(xml);
2930
+ if (RTEST(recover) && f_parse_str_ex) {
2931
+ /* Recover lane (ruby#465): a best-effort tree plus the
2932
+ * engine's parse diagnostics — never NULL for malformed
2933
+ * input, so the caller answers a document, not a raise. */
2934
+ struct leptris_parse_opts_mirror opts;
2935
+ memset(&opts, 0, sizeof(opts));
2936
+ opts.recover = 1;
2937
+ doc = f_parse_str_ex(RSTRING_PTR(xml),
2938
+ (size_t)RSTRING_LEN(xml), &opts, NULL);
2939
+ } else {
2940
+ doc = f_parse_str(RSTRING_PTR(xml),
2941
+ (size_t)RSTRING_LEN(xml), NULL);
2942
+ }
2908
2943
  return doc ? build_binding_document(doc) : Qnil;
2909
2944
  }
2910
2945
 
@@ -3401,9 +3436,16 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3401
3436
  * entry mutates only under the engine's document path. */
3402
3437
  rb_ext_ractor_safe(true);
3403
3438
 
3439
+ /* BEFORE any class definition — NativeNode/DocHandle/SaxParser
3440
+ * define against c_object. */
3441
+ c_object = rb_path2class("Object");
3442
+ c_arg_error = rb_path2class("ArgumentError");
3443
+ c_type_error = rb_path2class("TypeError");
3444
+ c_runtime_error = rb_path2class("RuntimeError");
3445
+
3404
3446
  m_leptris = rb_define_module("Leptris");
3405
3447
  m_xml = rb_define_module_under(m_leptris, "XML");
3406
- c_native_node = rb_define_class_under(m_xml, "NativeNode", rb_cObject);
3448
+ c_native_node = rb_define_class_under(m_xml, "NativeNode", c_object);
3407
3449
  rb_undef_alloc_func(c_native_node);
3408
3450
 
3409
3451
  id_iv_name = rb_intern("@name");
@@ -3485,7 +3527,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3485
3527
  id_sax_end_prefix = rb_intern("end_prefix_mapping");
3486
3528
  id_sax_error = rb_intern("error");
3487
3529
  c_sax_parser = rb_define_class_under(m_native, "SaxParser",
3488
- rb_cObject);
3530
+ c_object);
3489
3531
  rb_define_module_function(m_native, "sax_parse", nf_sax_parse, 3);
3490
3532
  rb_define_module_function(m_native, "sax_parser_create",
3491
3533
  nf_sax_parser_create, 2);
@@ -3545,7 +3587,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3545
3587
  rb_define_module_function(m_native, "set_binding_root",
3546
3588
  nf_set_binding_root, 2);
3547
3589
  rb_define_module_function(m_native, "parse_binding_document",
3548
- nf_parse_binding_document, 1);
3590
+ nf_parse_binding_document, -1);
3549
3591
  rb_define_module_function(m_native, "parse_fragment_addr",
3550
3592
  nf_parse_fragment_addr, 2);
3551
3593
  rb_define_module_function(m_native, "append_markup",
@@ -3579,7 +3621,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3579
3621
  nf_doc_handle_mark_absorbed, 1);
3580
3622
  rb_define_module_function(m_native, "create_binding_document",
3581
3623
  nf_create_binding_document, 0);
3582
- c_doc_handle = rb_define_class_under(m_xml, "DocHandle", rb_cObject);
3624
+ c_doc_handle = rb_define_class_under(m_xml, "DocHandle", c_object);
3583
3625
  rb_undef_alloc_func(c_doc_handle);
3584
3626
  rb_define_module_function(m_native, "bulk_attributes", nf_bulk_attributes, 1);
3585
3627
  rb_define_module_function(m_native, "bulk_attr_faces",
@@ -3598,14 +3640,16 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3598
3640
  nf_fast_element_xml, 4);
3599
3641
  }
3600
3642
 
3601
- #ifdef _WIN32
3602
3643
  /* MRI derives a C extension's init symbol from the require
3603
- * feature's basename cut at the first dot. The Windows per-minor
3604
- * DLLs are therefore named dot-free (native_3_3.so etc.), and
3605
- * each needs its exact init name exported. All are defined here
3606
- * unconditionally: a DLL only ever loads under the Ruby minor it
3607
- * was built against (PE binds its build Ruby's runtime DLL). */
3644
+ * feature's basename cut at the first dot. The per-minor artifacts
3645
+ * are therefore named dot-free (native_3_3.so / native_3_1.bundle
3646
+ * etc.), and each needs its exact init name exported. Defined
3647
+ * unconditionally on every platform since ruby#476: the darwin/
3648
+ * Linux per-minor bundles (native_<minor>) resolve these too —
3649
+ * an artifact only ever loads under the Ruby minor it was built
3650
+ * against. */
3651
+ LEPTRIS_INIT_EXPORT void Init_native_3_1(void) { Init_native(); }
3652
+ LEPTRIS_INIT_EXPORT void Init_native_3_2(void) { Init_native(); }
3608
3653
  LEPTRIS_INIT_EXPORT void Init_native_3_3(void) { Init_native(); }
3609
3654
  LEPTRIS_INIT_EXPORT void Init_native_3_4(void) { Init_native(); }
3610
3655
  LEPTRIS_INIT_EXPORT void Init_native_4_0(void) { Init_native(); }
3611
- #endif
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.361.0"
4
+ VERSION = "1.9.361.1"
5
5
  end
@@ -123,7 +123,7 @@ class Leptris::XML::Builder
123
123
  return @sink if markup.empty?
124
124
  case @sink
125
125
  when Leptris::XML::Document
126
- parsed = Leptris::XML::Document.parse(markup)
126
+ parsed = Leptris::XML::Document.parse(markup, strict: true)
127
127
  root = parsed.root
128
128
  if root.nil?
129
129
  raise Leptris::XML::Error,