lldb 0.1.0 → 0.3.0
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 +4 -4
- data/CHANGELOG.md +47 -3
- data/README.md +86 -2
- data/Rakefile +36 -0
- data/bindings/constants.yml +137 -0
- data/bindings/surface.yml +3072 -0
- data/ext/lldb/discovery.rb +101 -0
- data/ext/lldb/extconf.rb +206 -88
- data/ext/lldb/lldb_wrapper.cpp +7977 -856
- data/ext/lldb/lldb_wrapper.h +599 -324
- data/lib/lldb/address.rb +81 -0
- data/lib/lldb/api_support.rb +91 -0
- data/lib/lldb/attach_info.rb +122 -0
- data/lib/lldb/block.rb +121 -0
- data/lib/lldb/breakpoint.rb +11 -6
- data/lib/lldb/breakpoint_location.rb +20 -5
- data/lib/lldb/broadcaster.rb +79 -0
- data/lib/lldb/command_interpreter.rb +9 -4
- data/lib/lldb/command_return_object.rb +24 -5
- data/lib/lldb/compile_unit.rb +64 -0
- data/lib/lldb/context.rb +81 -0
- data/lib/lldb/debugger.rb +55 -12
- data/lib/lldb/error.rb +51 -3
- data/lib/lldb/event.rb +109 -0
- data/lib/lldb/expression_options.rb +124 -0
- data/lib/lldb/ffi_bindings.rb +326 -31
- data/lib/lldb/file_spec.rb +90 -0
- data/lib/lldb/file_spec_list.rb +78 -0
- data/lib/lldb/frame.rb +113 -18
- data/lib/lldb/function.rb +121 -0
- data/lib/lldb/instruction.rb +83 -0
- data/lib/lldb/instruction_list.rb +68 -0
- data/lib/lldb/launch_info.rb +145 -19
- data/lib/lldb/line_entry.rb +75 -0
- data/lib/lldb/listener.rb +96 -0
- data/lib/lldb/memory_region_info.rb +109 -0
- data/lib/lldb/module.rb +46 -5
- data/lib/lldb/native.rb +36 -0
- data/lib/lldb/native_buffer.rb +32 -0
- data/lib/lldb/native_handle.rb +60 -0
- data/lib/lldb/native_lifecycle.rb +64 -0
- data/lib/lldb/native_string_array.rb +34 -0
- data/lib/lldb/process.rb +121 -38
- data/lib/lldb/symbol.rb +117 -0
- data/lib/lldb/symbol_context.rb +9 -4
- data/lib/lldb/target.rb +73 -67
- data/lib/lldb/thread.rb +56 -19
- data/lib/lldb/type.rb +40 -3
- data/lib/lldb/type_member.rb +67 -0
- data/lib/lldb/types.rb +69 -4
- data/lib/lldb/value.rb +27 -17
- data/lib/lldb/value_list.rb +9 -5
- data/lib/lldb/version.rb +1 -1
- data/lib/lldb/watchpoint.rb +26 -9
- data/lib/lldb.rb +80 -10
- data/lldb.gemspec +4 -1
- data/rbs_collection.yaml +1 -1
- data/script/check_bindings +259 -0
- data/script/guard_c_exports +139 -0
- data/sig/lldb/ffi_bindings.rbs +258 -21
- data/sig/lldb/weak_ref.rbs +7 -0
- metadata +32 -6
- data/ext/lldb/Makefile +0 -24
- data/ext/lldb/liblldb_wrapper.dylib +0 -0
- data/ext/lldb/lldb_wrapper.o +0 -0
- data/ext/lldb/mkmf.log +0 -24
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'open3'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
require 'rbconfig'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
require 'tempfile'
|
|
10
|
+
require 'tmpdir'
|
|
11
|
+
require 'yaml'
|
|
12
|
+
|
|
13
|
+
ROOT = File.expand_path('..', __dir__)
|
|
14
|
+
|
|
15
|
+
options = {
|
|
16
|
+
header: File.join(ROOT, 'ext/lldb/lldb_wrapper.h'),
|
|
17
|
+
source: File.join(ROOT, 'ext/lldb/lldb_wrapper.cpp'),
|
|
18
|
+
ffi: File.join(ROOT, 'lib/lldb/ffi_bindings.rb'),
|
|
19
|
+
rbs: File.join(ROOT, 'sig/lldb/ffi_bindings.rbs'),
|
|
20
|
+
surface: File.join(ROOT, 'bindings/surface.yml'),
|
|
21
|
+
constants: File.join(ROOT, 'bindings/constants.yml'),
|
|
22
|
+
library: nil,
|
|
23
|
+
include: ENV['LLDB_INCLUDE'],
|
|
24
|
+
skip_library: false,
|
|
25
|
+
skip_constants: false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
OptionParser.new do |parser|
|
|
29
|
+
parser.banner = 'Usage: script/check_bindings [options]'
|
|
30
|
+
parser.on('--header PATH') { |value| options[:header] = value }
|
|
31
|
+
parser.on('--source PATH') { |value| options[:source] = value }
|
|
32
|
+
parser.on('--ffi PATH') { |value| options[:ffi] = value }
|
|
33
|
+
parser.on('--rbs PATH') { |value| options[:rbs] = value }
|
|
34
|
+
parser.on('--surface PATH') { |value| options[:surface] = value }
|
|
35
|
+
parser.on('--constants PATH') { |value| options[:constants] = value }
|
|
36
|
+
parser.on('--library PATH') { |value| options[:library] = value }
|
|
37
|
+
parser.on('--include PATH') { |value| options[:include] = value }
|
|
38
|
+
parser.on('--skip-library') { options[:skip_library] = true }
|
|
39
|
+
parser.on('--skip-constants') { options[:skip_constants] = true }
|
|
40
|
+
end.parse!
|
|
41
|
+
|
|
42
|
+
def read_required(path)
|
|
43
|
+
abort "missing binding input: #{path}" unless File.file?(path)
|
|
44
|
+
|
|
45
|
+
File.read(path)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def header_functions(source)
|
|
49
|
+
source = source.gsub(%r{/\*.*?\*/|//[^\n]*}, '')
|
|
50
|
+
source.scan(/(?:^|\n)\s*(?:[A-Za-z_][A-Za-z0-9_:<>*&\s]*?)\s+(lldb_[A-Za-z0-9_]+)\s*\([^;]*\)\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?;/m).flatten.uniq.sort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ffi_functions(source)
|
|
54
|
+
source.scan(/\battach_function\s+:([a-z0-9_]+)/).flatten.uniq.sort
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def rbs_functions(source)
|
|
58
|
+
source.scan(/\bdef self\.(lldb_[a-z0-9_]+):/).flatten.uniq.sort
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def exported_functions(path)
|
|
62
|
+
command = if RbConfig::CONFIG['host_os'].match?(/darwin/)
|
|
63
|
+
['nm', '-gU', path]
|
|
64
|
+
else
|
|
65
|
+
['nm', '-D', '--defined-only', path]
|
|
66
|
+
end
|
|
67
|
+
stdout, stderr, status = Open3.capture3(*command)
|
|
68
|
+
abort "cannot inspect native library #{path}: #{stderr.strip}" unless status.success?
|
|
69
|
+
|
|
70
|
+
stdout.lines.filter_map do |line|
|
|
71
|
+
name = line.split.last
|
|
72
|
+
next unless name&.match?(/\A_?lldb_[A-Za-z0-9_]+\z/)
|
|
73
|
+
|
|
74
|
+
name.delete_prefix('_').to_sym.to_s
|
|
75
|
+
end.uniq.sort
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def report_difference(label, left_name, left, right_name, right)
|
|
79
|
+
missing = left - right
|
|
80
|
+
extra = right - left
|
|
81
|
+
return [] if missing.empty? && extra.empty?
|
|
82
|
+
|
|
83
|
+
messages = []
|
|
84
|
+
messages << "#{label}: missing from #{right_name}: #{missing.join(', ')}" unless missing.empty?
|
|
85
|
+
messages << "#{label}: missing from #{left_name}: #{extra.join(', ')}" unless extra.empty?
|
|
86
|
+
messages
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def matching_parenthesis(source, opening)
|
|
90
|
+
depth = 0
|
|
91
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
92
|
+
depth += 1 if byte == 40
|
|
93
|
+
depth -= 1 if byte == 41
|
|
94
|
+
return opening + offset if depth.zero?
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def function_body(source, name)
|
|
100
|
+
opening = nil
|
|
101
|
+
source.to_enum(:scan, /\b#{Regexp.escape(name)}\s*\(/m).each do
|
|
102
|
+
match = Regexp.last_match
|
|
103
|
+
opening_parenthesis = match.begin(0) + match[0].index('(')
|
|
104
|
+
closing_parenthesis = matching_parenthesis(source, opening_parenthesis)
|
|
105
|
+
next unless closing_parenthesis
|
|
106
|
+
|
|
107
|
+
suffix = source.byteslice((closing_parenthesis + 1)..).to_s
|
|
108
|
+
opening_offset = suffix.index(/\A\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?\{/m)
|
|
109
|
+
next unless opening_offset
|
|
110
|
+
|
|
111
|
+
opening = closing_parenthesis + 1 + opening_offset + suffix[opening_offset..].index('{')
|
|
112
|
+
break
|
|
113
|
+
end
|
|
114
|
+
return nil unless opening
|
|
115
|
+
|
|
116
|
+
depth = 0
|
|
117
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
118
|
+
depth += 1 if byte == 123
|
|
119
|
+
depth -= 1 if byte == 125
|
|
120
|
+
return source.byteslice(opening, offset + 1) if depth.zero?
|
|
121
|
+
end
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def check_surface(path, functions, source)
|
|
126
|
+
document = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
127
|
+
entries = document.fetch('entries')
|
|
128
|
+
errors = []
|
|
129
|
+
|
|
130
|
+
errors << "surface ledger has missing entries: #{(functions - entries.keys).sort.join(', ')}" unless
|
|
131
|
+
(functions - entries.keys).empty?
|
|
132
|
+
errors << "surface ledger has stale entries: #{(entries.keys - functions).sort.join(', ')}" unless
|
|
133
|
+
(entries.keys - functions).empty?
|
|
134
|
+
|
|
135
|
+
allowed_classifications = %w[public internal metadata optional_stub deprecated]
|
|
136
|
+
entries.each do |name, entry|
|
|
137
|
+
next unless functions.include?(name)
|
|
138
|
+
|
|
139
|
+
classification = entry.fetch('classification', '')
|
|
140
|
+
unless allowed_classifications.include?(classification)
|
|
141
|
+
errors << "surface ledger entry #{name} has invalid classification #{classification.inspect}"
|
|
142
|
+
end
|
|
143
|
+
if entry.fetch('reason', '').to_s.strip.empty?
|
|
144
|
+
errors << "surface ledger entry #{name} has no reason"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
guard = entry.fetch('exception_guard', {})
|
|
148
|
+
kind = guard.fetch('kind', '')
|
|
149
|
+
reason = guard.fetch('reason', '').to_s.strip
|
|
150
|
+
if reason.empty?
|
|
151
|
+
errors << "surface ledger entry #{name} has no exception guard reason"
|
|
152
|
+
elsif kind == 'error_boundary'
|
|
153
|
+
body = function_body(source, name)
|
|
154
|
+
unless body&.include?('try') && body.include?('catch')
|
|
155
|
+
errors << "#{name} is marked error_boundary but has no try/catch body"
|
|
156
|
+
end
|
|
157
|
+
unless source.match?(/\b#{Regexp.escape(name)}\s*\([^;]*\)\s*LLDB_WRAPPER_NOEXCEPT\s*\{/m)
|
|
158
|
+
errors << "#{name} is marked error_boundary but is not declared noexcept"
|
|
159
|
+
end
|
|
160
|
+
elsif kind != 'noexcept'
|
|
161
|
+
errors << "surface ledger entry #{name} has invalid exception guard #{kind.inspect}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
methods = document.fetch('ruby_methods', [])
|
|
166
|
+
methods.each do |mapping|
|
|
167
|
+
file = File.join(ROOT, mapping.fetch('file'))
|
|
168
|
+
method = mapping.fetch('method')
|
|
169
|
+
unless File.file?(file) && File.read(file).match?(/^\s*def #{Regexp.escape(method)}\b/)
|
|
170
|
+
errors << "#{mapping.fetch('function')} maps to missing Ruby method #{mapping.fetch('method')} in #{file}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
errors
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def find_include_dir(explicit)
|
|
177
|
+
return explicit if explicit && File.file?(File.join(explicit, 'lldb/API/LLDB.h'))
|
|
178
|
+
|
|
179
|
+
discovery = File.join(ROOT, 'ext/lldb/discovery.rb')
|
|
180
|
+
require discovery
|
|
181
|
+
LLDB::BuildDiscovery.candidates.find do |candidate|
|
|
182
|
+
File.file?(File.join(candidate.include_dir.to_s, 'lldb/API/LLDB.h'))
|
|
183
|
+
end&.include_dir
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def check_constants(path, include_dir)
|
|
187
|
+
document = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
188
|
+
constants = document.fetch('constants')
|
|
189
|
+
return [] if constants.empty?
|
|
190
|
+
|
|
191
|
+
unless include_dir
|
|
192
|
+
return ['cannot check constants: LLDB include directory was not found']
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
source = <<~CPP
|
|
196
|
+
#include <lldb/API/LLDB.h>
|
|
197
|
+
#include <iostream>
|
|
198
|
+
int main() {
|
|
199
|
+
#{constants.map { |entry| " std::cout << static_cast<unsigned long long>(#{entry.fetch('native')}) << '\\n';" }.join("\n")}
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
CPP
|
|
203
|
+
|
|
204
|
+
Dir.mktmpdir('lldb-ruby-constants') do |directory|
|
|
205
|
+
source_path = File.join(directory, 'probe.cpp')
|
|
206
|
+
binary_path = File.join(directory, 'probe')
|
|
207
|
+
File.write(source_path, source)
|
|
208
|
+
compiler = Shellwords.split(ENV.fetch('CXX', RbConfig::CONFIG['CXX'] || 'c++'))
|
|
209
|
+
_stdout, stderr, status = Open3.capture3(
|
|
210
|
+
*compiler, '-std=c++17', '-I', include_dir, source_path, '-o', binary_path
|
|
211
|
+
)
|
|
212
|
+
return ["constant probe compilation failed: #{stderr.strip}"] unless status.success?
|
|
213
|
+
|
|
214
|
+
native_output, native_error, native_status = Open3.capture3(binary_path)
|
|
215
|
+
return ["constant probe failed: #{native_error.strip}"] unless native_status.success?
|
|
216
|
+
|
|
217
|
+
ruby_code = constants.map { |entry| "puts LLDB::#{entry.fetch('ruby')}" }.join('; ')
|
|
218
|
+
ruby_output, ruby_error, ruby_status = Open3.capture3(
|
|
219
|
+
RbConfig.ruby, '-I', File.join(ROOT, 'lib'), '-r', 'lldb/types', '-e', ruby_code
|
|
220
|
+
)
|
|
221
|
+
return ["Ruby constant probe failed: #{ruby_error.strip}"] unless ruby_status.success?
|
|
222
|
+
|
|
223
|
+
native_values = native_output.lines.map(&:strip)
|
|
224
|
+
ruby_values = ruby_output.lines.map(&:strip)
|
|
225
|
+
errors = []
|
|
226
|
+
constants.each_with_index do |entry, index|
|
|
227
|
+
next if native_values[index] == ruby_values[index]
|
|
228
|
+
|
|
229
|
+
errors << "constant #{entry.fetch('ruby')} differs: Ruby=#{ruby_values[index].inspect}, " \
|
|
230
|
+
"LLDB=#{native_values[index].inspect}"
|
|
231
|
+
end
|
|
232
|
+
errors
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
header = header_functions(read_required(options[:header]))
|
|
237
|
+
ffi = ffi_functions(read_required(options[:ffi]))
|
|
238
|
+
rbs = rbs_functions(read_required(options[:rbs]))
|
|
239
|
+
source = read_required(options[:source])
|
|
240
|
+
errors = []
|
|
241
|
+
errors.concat(report_difference('header/FFI', 'header', header, 'FFI', ffi))
|
|
242
|
+
errors.concat(report_difference('FFI/RBS', 'FFI', ffi, 'RBS', rbs))
|
|
243
|
+
|
|
244
|
+
unless options[:skip_library]
|
|
245
|
+
library = options[:library] || Dir[File.join(ROOT, 'lib/lldb/liblldb_wrapper.{so,dylib,dll}')].first
|
|
246
|
+
errors << 'native library was not found; pass --library or --skip-library' unless library
|
|
247
|
+
errors.concat(report_difference('header/native exports', 'header', header, 'native exports', exported_functions(library))) if library
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
errors.concat(check_surface(options[:surface], header, source))
|
|
251
|
+
errors.concat(check_constants(options[:constants], find_include_dir(options[:include]))) unless options[:skip_constants]
|
|
252
|
+
|
|
253
|
+
if errors.empty?
|
|
254
|
+
puts "binding parity OK (#{header.length} functions)"
|
|
255
|
+
exit 0
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
warn errors.map { |error| "error: #{error}" }.join("\n")
|
|
259
|
+
exit 1
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
ROOT = File.expand_path('..', __dir__)
|
|
7
|
+
HEADER = File.join(ROOT, 'ext/lldb/lldb_wrapper.h')
|
|
8
|
+
SOURCE = File.join(ROOT, 'ext/lldb/lldb_wrapper.cpp')
|
|
9
|
+
SURFACE = File.join(ROOT, 'bindings/surface.yml')
|
|
10
|
+
|
|
11
|
+
options = { write: false }
|
|
12
|
+
OptionParser.new do |parser|
|
|
13
|
+
parser.banner = 'Usage: script/guard_c_exports [--write]'
|
|
14
|
+
parser.on('--write', 'Rewrite the C ABI exports with exception guards') { options[:write] = true }
|
|
15
|
+
end.parse!
|
|
16
|
+
|
|
17
|
+
def declarations(source)
|
|
18
|
+
source = source.gsub(%r{/\*.*?\*/|//[^\n]*}, '')
|
|
19
|
+
pattern = /(?:^|\n)\s*(?<return_type>[A-Za-z_][A-Za-z0-9_:<>*&\s]*?)\s+(?<name>lldb_[A-Za-z0-9_]+)\s*\((?<arguments>[^;]*)\)\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?;/m
|
|
20
|
+
source.to_enum(:scan, pattern).map do
|
|
21
|
+
Regexp.last_match.named_captures.transform_values(&:strip)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def matching_parenthesis(source, opening)
|
|
26
|
+
depth = 0
|
|
27
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
28
|
+
depth += 1 if byte == 40
|
|
29
|
+
depth -= 1 if byte == 41
|
|
30
|
+
return opening + offset if depth.zero?
|
|
31
|
+
end
|
|
32
|
+
raise "unclosed function parameter list at #{opening}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def matching_brace(source, opening)
|
|
36
|
+
depth = 0
|
|
37
|
+
source.byteslice(opening..).each_byte.with_index do |byte, offset|
|
|
38
|
+
depth += 1 if byte == 123
|
|
39
|
+
depth -= 1 if byte == 125
|
|
40
|
+
return opening + offset if depth.zero?
|
|
41
|
+
end
|
|
42
|
+
raise "unclosed function body at #{opening}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def definition(source, name)
|
|
46
|
+
pattern = /\b#{Regexp.escape(name)}\s*\(/m
|
|
47
|
+
source.to_enum(:scan, pattern).each do
|
|
48
|
+
opening = Regexp.last_match.begin(0) + Regexp.last_match[0].index('(')
|
|
49
|
+
closing = matching_parenthesis(source, opening)
|
|
50
|
+
after_parameters = source.byteslice((closing + 1)..).to_s
|
|
51
|
+
next unless after_parameters =~ /\A\s*(?:LLDB_WRAPPER_NOEXCEPT\s*)?\{/m
|
|
52
|
+
|
|
53
|
+
body_opening = closing + 1 + Regexp.last_match[0].index('{')
|
|
54
|
+
return [body_opening, matching_brace(source, body_opening)]
|
|
55
|
+
end
|
|
56
|
+
raise "definition not found for #{name}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def guarded_body?(source, opening)
|
|
60
|
+
source.byteslice((opening + 1)..).to_s.match?(/\A\s*try\s*\{/)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def catch_block(return_type)
|
|
64
|
+
failure_return = if return_type == 'void'
|
|
65
|
+
''
|
|
66
|
+
elsif return_type == 'lldb_ruby_status_t'
|
|
67
|
+
' return LLDB_RUBY_STATUS_INTERNAL_ERROR;' + 10.chr
|
|
68
|
+
else
|
|
69
|
+
' return {};' + 10.chr
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
<<~CPP
|
|
73
|
+
|
|
74
|
+
} catch (const std::bad_alloc&) {
|
|
75
|
+
wrapper_set_error_state("native allocation failed across the C ABI");
|
|
76
|
+
#{failure_return} } catch (const std::exception& exception) {
|
|
77
|
+
wrapper_set_error_state(exception.what());
|
|
78
|
+
#{failure_return} } catch (...) {
|
|
79
|
+
wrapper_set_error_state("unknown native exception across the C ABI");
|
|
80
|
+
#{failure_return} }
|
|
81
|
+
CPP
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def rewrite_header(header, entries)
|
|
85
|
+
rewritten = header.dup
|
|
86
|
+
entries.reverse_each do |entry|
|
|
87
|
+
pattern = /\b#{Regexp.escape(entry.fetch('name'))}\s*\([^;]*?\)\s*/m
|
|
88
|
+
match = rewritten.match(pattern)
|
|
89
|
+
raise "declaration not found for #{entry.fetch('name')}" unless match
|
|
90
|
+
next if rewritten.byteslice(match.end(0), 40).to_s.match?(/\A\s*LLDB_WRAPPER_NOEXCEPT\b/)
|
|
91
|
+
|
|
92
|
+
rewritten.insert(match.end(0), ' LLDB_WRAPPER_NOEXCEPT')
|
|
93
|
+
end
|
|
94
|
+
rewritten
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def rewrite_source(source, entries)
|
|
98
|
+
edits = entries.map do |entry|
|
|
99
|
+
opening, closing = definition(source, entry.fetch('name'))
|
|
100
|
+
next if guarded_body?(source, opening)
|
|
101
|
+
|
|
102
|
+
noexcept = source.byteslice((opening - 80)...opening).to_s.match?(/LLDB_WRAPPER_NOEXCEPT\s*$/)
|
|
103
|
+
[
|
|
104
|
+
[opening, (noexcept ? '' : ' LLDB_WRAPPER_NOEXCEPT')],
|
|
105
|
+
[opening + 1, "\n try {"],
|
|
106
|
+
[closing, catch_block(entry.fetch('return_type'))]
|
|
107
|
+
]
|
|
108
|
+
end.compact.flatten(1).sort_by(&:first).reverse
|
|
109
|
+
|
|
110
|
+
rewritten = source.dup
|
|
111
|
+
edits.each { |offset, text| rewritten.insert(offset, text) }
|
|
112
|
+
rewritten
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def rewrite_surface(surface)
|
|
116
|
+
needle = ['kind: reviewed_no_throw', ' reason: The export is tracked explicitly until its native error boundary is upgraded.'].join(10.chr)
|
|
117
|
+
replacement = ['kind: error_boundary', ' reason: Every export is noexcept and catches native C++ exceptions before crossing the C ABI.'].join(10.chr)
|
|
118
|
+
|
|
119
|
+
surface.gsub(needle, replacement)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
entries = declarations(File.read(HEADER))
|
|
123
|
+
abort "expected 482 declarations, found #{entries.length}" unless entries.length == 482
|
|
124
|
+
|
|
125
|
+
rewritten_header = rewrite_header(File.read(HEADER), entries)
|
|
126
|
+
rewritten_source = rewrite_source(File.read(SOURCE), entries)
|
|
127
|
+
rewritten_surface = rewrite_surface(File.read(SURFACE))
|
|
128
|
+
|
|
129
|
+
if options[:write]
|
|
130
|
+
File.write(HEADER, rewritten_header)
|
|
131
|
+
File.write(SOURCE, rewritten_source)
|
|
132
|
+
File.write(SURFACE, rewritten_surface)
|
|
133
|
+
puts "guarded #{entries.length} C ABI exports"
|
|
134
|
+
else
|
|
135
|
+
abort 'header requires exception-guard updates' unless rewritten_header == File.read(HEADER)
|
|
136
|
+
abort 'source requires exception-guard updates' unless rewritten_source == File.read(SOURCE)
|
|
137
|
+
abort 'surface ledger requires exception-guard updates' unless rewritten_surface == File.read(SURFACE)
|
|
138
|
+
puts "all #{entries.length} C ABI exports are guarded"
|
|
139
|
+
end
|