Module: Audition::Reconciliation

Defined in:
lib/audition/reconciliation.rb

Overview

What the dynamic probe proved shareable retires the static pass's guesses about the same objects. A constant the probe read as shareable needs no static warning at all; class-level state whose every value was shareable after boot keeps its finding as an info note, because reads are legal from any Ractor and only a later write would raise.

Constant Summary collapse

PROVEN_WHY =
"The dynamic probe found every value this variable held " \
"after boot shareable, so reads from any Ractor are legal; " \
"only a later write would raise Ractor::IsolationError, " \
"and writes must stay on the main Ractor."
PROVEN_FIX =
"Keep every write at boot on the main Ractor, or warm the " \
"memo before spawning Ractors."

Class Method Summary collapse

Class Method Details

.apply(findings, results) ⇒ Array<Finding>

Returns findings with the disproven ones dropped or downgraded.

Parameters:

Returns:

  • (Array<Finding>) —

    findings with the disproven ones dropped or downgraded



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/audition/reconciliation.rb', line 24

def self.apply(findings, results)
  constants, ivars = proven(results)
  return findings if constants.empty? && ivars.empty?

  findings.filter_map do |finding|
    case finding.check
    when "mutable-constants"
      site = [realpath(finding.path), finding.line]
      constants.include?(site) ? nil : finding
    when "class-level-state"
      if finding.severity != :info && finding.subject &&
          ivars.include?(finding.subject)
        finding.with(
          severity: :info,
          message: "#{finding.message}, shareable in the " \
                   "dynamic probe",
          why: PROVEN_WHY,
          fix: PROVEN_FIX
        )
      else
        finding
      end
    else
      finding
    end
  end
end

.proven(results) ⇒ Array(Set, Set)

Returns constant sites ([path, line]) and class-level ivars ("Owner/@name") observed shareable.

Returns:

  • (Array(Set, Set)) —

    constant sites ([path, line]) and class-level ivars ("Owner/@name") observed shareable



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/audition/reconciliation.rb', line 54

def self.proven(results)
  constants = Set.new
  ivars = Set.new
  results.each do |result|
    raw = result.raw
    next unless raw.is_a?(Hash)

    Array(raw["proven_constants"]).each do |path, line|
      constants << [realpath(path), line]
    end
    Array(raw["class_state"]).each do |entry|
      unshareable = Array(entry["unshareable"])
      owner = entry["const"].to_s
      Array(entry["ivars"]).each do |ivar|
        next if unshareable.include?(ivar)

        ivars << "#{owner}/#{ivar}"
        # A write through a singleton attribute only knows
        # its owner's last segment.
        ivars << "#{owner.split("::").last}/#{ivar}"
      end
    end
  end
  [constants, ivars]
end

.realpath(path) ⇒ Object



80
81
82
83
84
# File 'lib/audition/reconciliation.rb', line 80

def self.realpath(path)
  File.realpath(path)
rescue SystemCallError, TypeError
  path
end