Class: Audition::Static::NativeExtensions

Inherits:
Object
  • Object
show all
Defined in:
lib/audition/static/native_extensions.rb

Overview

Compiled extensions are invisible to the Ruby scanner, but the one fact that decides their Ractor behavior is visible from outside. Ruby marks every method an extension defines while Init_* runs without rb_ext_ractor_safe(true), and calling any of them from a non-main Ractor raises Ractor::UnsafeError. The declaration is a libruby import, so a compiled file names the symbol or does not, whatever language produced it; sources spell it as rb_ext_ractor_safe(true) or the RB_EXT_RACTOR_SAFE(true) macro (C, Rust via rb-sys, Zig).

Compiled files win when present: they are what require loads. Sources are consulted only for a checkout that has not been built yet.

Constant Summary collapse

CHECK =
"native-extension"
SYMBOL =
"rb_ext_ractor_safe"
DECLARATION =
/rb_ext_ractor_safe\s*\(\s*true\s*\)/i
INIT =
/\bInit_\w+\s*\(/
SOURCES =
"*.{c,cc,cpp,cxx,m,mm,h,hpp,rs,zig}"
BUILD_FILES =
%w[Cargo.toml build.zig extconf.rb].freeze
HARNESS_DIRS =

Harness crates and test trees are not compiled into the extension; a fuzz target that links the VM itself may call rb_ext_ractor_safe without the extension doing so.

%w[fuzz benches tests examples test spec].freeze
SILENT_WHY =
"Ruby marks every method an extension defines while " \
"Init_* runs without rb_ext_ractor_safe(true), and calling " \
"any of them from a non-main Ractor raises " \
"Ractor::UnsafeError (\"ractor unsafe method called from " \
"not main ractor\"), whether the extension is C, Rust, or " \
"Zig (verified on Ruby 4.0.6). "
COMPILED_TAIL =

Tails appended to SILENT_WHY at finding time; a constant built with + would hold an unfrozen String.

"The declaration is a libruby import, so a compiled file " \
"that never names the symbol cannot have made it."
SOURCE_TAIL =
"These sources never call it, so the compiled extension " \
"will raise the same way."
SILENT_FIX =
"Audit the native code for process-global mutable state " \
"(statics, caches, VALUEs held outside Ruby objects), then " \
"call RB_EXT_RACTOR_SAFE(true) first thing in Init_* " \
"(rb_sys::rb_ext_ractor_safe(true) from Rust). Until then, " \
"keep every call into this extension on the main Ractor."
DECLARED_WHY =
"rb_ext_ractor_safe(true) is the maintainer's assertion " \
"that the extension keeps no process-global mutable state; " \
"Ruby does not verify it and neither can Audition. Its " \
"methods run from any Ractor, in parallel, on the strength " \
"of that assertion alone."
DECLARED_FIX =
"Exercise real calls from a non-main Ractor (a script " \
"probe, or the gem's test suite under Ractor.new) before " \
"relying on it."

Instance Method Summary collapse

Instance Method Details

#analyze(target, compiled_files: target.compiled_files) ⇒ Array<Finding>

Parameters:

  • target (Target)
  • compiled_files (Array<String>) (defaults to: target.compiled_files) —

    compiled extension files to inspect (defaults to the target's own list; the CLI passes the config-filtered subset)

Returns:



67
68
69
70
71
72
73
74
75
# File 'lib/audition/static/native_extensions.rb', line 67

def analyze(target, compiled_files: target.compiled_files)
  if compiled_files.any?
    return compiled_files.filter_map { |p| compiled_finding(p) }
  end

  extension_dirs(target.root).map do |dir|
    source_finding(dir, target.root)
  end
end