Class: Ibex::VerificationReport::CanonicalIR

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/verification_report/canonical_ir.rb,
sig/ibex/verification_report/canonical_ir.rbs

Overview

Rebuilds immutable IR with source locations expressed as report logical identities.

Constant Summary collapse

LOGICAL_ROOT =

RBS:

  • type json_value = String | Integer | Float | bool | nil | Array[json_value] | Hash[json_value, json_value]

Returns:

  • (String)
"input"

Instance Method Summary collapse

Constructor Details

#initialize(automaton, source_records:) ⇒ CanonicalIR

Returns a new instance of CanonicalIR.

RBS:

  • (IR::Automaton automaton, source_records: Array[GenerationInput]) -> void

Parameters:



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/ibex/verification_report/canonical_ir.rb', line 17

def initialize(automaton, source_records:)
  raise ArgumentError, "source_records must not be empty" if source_records.empty?
  if source_records.length > LogicalPath::MAX_INPUT_FILES
    raise ArgumentError, "verification reports support at most #{LogicalPath::MAX_INPUT_FILES} input files"
  end

  @automaton = automaton
  @logical_files = source_records.map.with_index do |record, index|
    [record, LogicalPath.input(record.path, index)]
  end
end

Instance Method Details

#absolute_file?(file) ⇒ Boolean

RBS:

  • (String file) -> bool

Parameters:

  • file (String)

Returns:

  • (Boolean)


113
114
115
# File 'lib/ibex/verification_report/canonical_ir.rb', line 113

def absolute_file?(file)
  file.start_with?("/") || file.match?(%r{\A[A-Za-z]:[\\/]})
end

#buildIR::Automaton

RBS:

  • () -> IR::Automaton

Returns:



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/ibex/verification_report/canonical_ir.rb', line 30

def build
  document = JSON.parse(IR::Serialize.dump(@automaton))
  normalize_source_identity!(document)
  grammar = IR::Validator.validate(generate_json(document.fetch("grammar")))
  raise ArgumentError, "canonical bundle grammar did not validate" unless grammar.is_a?(IR::Grammar)

  document["grammar_digest"] = digest(grammar)
  automaton = IR::Validator.validate(generate_json(document))
  raise ArgumentError, "canonical bundle automaton did not validate" unless automaton.is_a?(IR::Automaton)

  automaton
end

#digest(grammar) ⇒ String

RBS:

  • (IR::Grammar grammar) -> String

Parameters:

Returns:

  • (String)


118
119
120
# File 'lib/ibex/verification_report/canonical_ir.rb', line 118

def digest(grammar)
  "sha256:#{Digest::SHA256.hexdigest(IR::Serialize.dump(grammar))}"
end

#generate_json(value) ⇒ String

RBS:

  • (json_value value) -> String

Parameters:

  • value (json_value)

Returns:

  • (String)


64
65
66
# File 'lib/ibex/verification_report/canonical_ir.rb', line 64

def generate_json(value)
  JSON.generate(normalize_for_json(value))
end

#logical_file(file) ⇒ String

RBS:

  • (String file) -> String

Parameters:

  • file (String)

Returns:

  • (String)


87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ibex/verification_report/canonical_ir.rb', line 87

def logical_file(file)
  file_bytes = file.b
  logical = @logical_files.find { |_record, path| path.b == file_bytes }
  return logical.fetch(1) if logical

  candidates = @logical_files.select { |record, _path| record_matches?(record, file) }
  return candidates.fetch(0).fetch(1) if candidates.one?

  detail = candidates.empty? ? "is not present in source_records" : "matches multiple source_records"
  raise ArgumentError, "IR source location #{file.inspect} #{detail}"
end

#normalize_for_json(value) ⇒ json_value

RBS:

  • (json_value value) -> json_value

Parameters:

  • value (json_value)

Returns:

  • (json_value)


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ibex/verification_report/canonical_ir.rb', line 69

def normalize_for_json(value)
  case value
  when String
    normalized = value.dup.force_encoding(Encoding::UTF_8)
    normalized.valid_encoding? ? normalized : normalized.scrub
  when Array
    value.map { |child| normalize_for_json(child) }
  when Hash
    normalized = {} #: Hash[json_value, json_value] # steep:ignore UnannotatedEmptyCollection
    value.each_with_object(normalized) do |(key, child), result|
      result[normalize_for_json(key)] = normalize_for_json(child)
    end
  else
    value
  end
end

#normalize_source_identity!(value) ⇒ json_value

RBS:

  • (json_value value) -> json_value

Parameters:

  • value (json_value)

Returns:

  • (json_value)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ibex/verification_report/canonical_ir.rb', line 46

def normalize_source_identity!(value)
  case value
  when Array then value.each { |child| normalize_source_identity!(child) }
  when Hash
    value.each do |key, child|
      value[key] = if key == "file" && child.is_a?(String)
                     logical_file(child)
                   elsif key == "root" && child.is_a?(String)
                     LOGICAL_ROOT
                   else
                     normalize_source_identity!(child)
                   end
    end
  end
  value
end

#record_matches?(record, file) ⇒ Boolean

RBS:

  • (GenerationInput record, String file) -> bool

Parameters:

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ibex/verification_report/canonical_ir.rb', line 100

def record_matches?(record, file)
  paths = [record.path, *record.access_paths].map(&:b)
  file_bytes = file.b
  return true if paths.include?(file_bytes)
  return true if absolute_file?(file) && paths.include?(File.expand_path(file).b)

  suffix = "/".b + file_bytes
  !absolute_file?(file) && paths.any? do |path|
    path == file_bytes || path.end_with?(suffix)
  end
end