lldb 0.2.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -4
  3. data/README.md +86 -2
  4. data/Rakefile +36 -0
  5. data/bindings/constants.yml +137 -0
  6. data/bindings/surface.yml +3072 -0
  7. data/ext/lldb/discovery.rb +101 -0
  8. data/ext/lldb/extconf.rb +206 -88
  9. data/ext/lldb/lldb_wrapper.cpp +7934 -877
  10. data/ext/lldb/lldb_wrapper.h +596 -333
  11. data/lib/lldb/address.rb +81 -0
  12. data/lib/lldb/api_support.rb +23 -6
  13. data/lib/lldb/attach_info.rb +122 -0
  14. data/lib/lldb/block.rb +121 -0
  15. data/lib/lldb/breakpoint.rb +11 -6
  16. data/lib/lldb/breakpoint_location.rb +20 -5
  17. data/lib/lldb/broadcaster.rb +79 -0
  18. data/lib/lldb/command_interpreter.rb +9 -4
  19. data/lib/lldb/command_return_object.rb +24 -5
  20. data/lib/lldb/compile_unit.rb +64 -0
  21. data/lib/lldb/context.rb +81 -0
  22. data/lib/lldb/debugger.rb +55 -12
  23. data/lib/lldb/error.rb +50 -3
  24. data/lib/lldb/event.rb +109 -0
  25. data/lib/lldb/expression_options.rb +124 -0
  26. data/lib/lldb/ffi_bindings.rb +313 -31
  27. data/lib/lldb/file_spec.rb +90 -0
  28. data/lib/lldb/file_spec_list.rb +78 -0
  29. data/lib/lldb/frame.rb +109 -17
  30. data/lib/lldb/function.rb +121 -0
  31. data/lib/lldb/instruction.rb +83 -0
  32. data/lib/lldb/instruction_list.rb +68 -0
  33. data/lib/lldb/launch_info.rb +145 -19
  34. data/lib/lldb/line_entry.rb +75 -0
  35. data/lib/lldb/listener.rb +96 -0
  36. data/lib/lldb/memory_region_info.rb +8 -3
  37. data/lib/lldb/module.rb +46 -5
  38. data/lib/lldb/native.rb +36 -0
  39. data/lib/lldb/native_buffer.rb +32 -0
  40. data/lib/lldb/native_handle.rb +60 -0
  41. data/lib/lldb/native_lifecycle.rb +64 -0
  42. data/lib/lldb/native_string_array.rb +34 -0
  43. data/lib/lldb/process.rb +102 -40
  44. data/lib/lldb/symbol.rb +117 -0
  45. data/lib/lldb/symbol_context.rb +9 -4
  46. data/lib/lldb/target.rb +69 -67
  47. data/lib/lldb/thread.rb +51 -18
  48. data/lib/lldb/type.rb +40 -3
  49. data/lib/lldb/type_member.rb +67 -0
  50. data/lib/lldb/types.rb +69 -4
  51. data/lib/lldb/value.rb +27 -17
  52. data/lib/lldb/value_list.rb +9 -5
  53. data/lib/lldb/version.rb +1 -1
  54. data/lib/lldb/watchpoint.rb +26 -9
  55. data/lib/lldb.rb +78 -10
  56. data/lldb.gemspec +4 -1
  57. data/rbs_collection.yaml +1 -1
  58. data/script/check_bindings +259 -0
  59. data/script/guard_c_exports +139 -0
  60. data/sig/lldb/ffi_bindings.rbs +247 -21
  61. data/sig/lldb/weak_ref.rbs +7 -0
  62. metadata +30 -6
  63. data/ext/lldb/Makefile +0 -24
  64. data/ext/lldb/liblldb_wrapper.dylib +0 -0
  65. data/ext/lldb/lldb_wrapper.o +0 -0
  66. data/ext/lldb/mkmf.log +0 -24
@@ -22,20 +22,21 @@ module LLDB
22
22
  end
23
23
 
24
24
  class LaunchInfo
25
+ prepend NativeLifecycle
26
+
25
27
  # @rbs args: Array[String]?
26
28
  # @rbs return: void
27
- def initialize(args = nil)
28
- argv = nil
29
- if args && !args.empty?
30
- argv = FFI::MemoryPointer.new(:pointer, args.length + 1)
31
- args.each_with_index do |arg, i|
32
- argv[i].put_pointer(0, FFI::MemoryPointer.from_string(arg))
33
- end
34
- argv[args.length].put_pointer(0, nil)
35
- end
29
+ def initialize(args = nil, context: nil)
30
+ @arguments = if args && !args.empty?
31
+ NativeStringArray.new(args)
32
+ end
36
33
 
37
- @ptr = FFIBindings.lldb_launch_info_create(argv) # : FFI::Pointer
38
- ObjectSpace.define_finalizer(self, self.class.release(@ptr))
34
+ ptr = FFIBindings.lldb_launch_info_create(@arguments&.to_ptr)
35
+ initialize_native_object(
36
+ ptr,
37
+ release: ->(released) { FFIBindings.lldb_launch_info_destroy(released) },
38
+ context: context
39
+ )
39
40
  end
40
41
 
41
42
  # @rbs ptr: FFI::Pointer
@@ -44,26 +45,151 @@ module LLDB
44
45
  ->(_id) { FFIBindings.lldb_launch_info_destroy(ptr) unless ptr.null? }
45
46
  end
46
47
 
48
+ # @rbs return: bool
49
+ def valid?
50
+ !@ptr.null?
51
+ end
52
+
47
53
  # @rbs dir: String
48
54
  # @rbs return: void
49
55
  def working_directory=(dir)
56
+ NativeStringArray.validate!(dir)
50
57
  FFIBindings.lldb_launch_info_set_working_directory(@ptr, dir)
51
58
  end
52
59
 
53
- # @rbs env: Hash[String, String]
60
+ # @rbs return: String?
61
+ def working_directory
62
+ FFIBindings.lldb_launch_info_get_working_directory(@ptr)
63
+ end
64
+
65
+ # @rbs env: Hash[String, String]?
54
66
  # @rbs append: bool
55
67
  # @rbs return: void
56
68
  def set_environment(env, append: true)
57
- return unless env && !env.empty?
69
+ return if env.nil?
70
+ raise ArgumentError, 'environment must be a Hash' unless env.is_a?(Hash)
71
+
72
+ entries = env.map do |key, value|
73
+ key = NativeStringArray.validate!(key)
74
+ value = NativeStringArray.validate!(value)
75
+ raise ArgumentError, 'environment keys must not contain =' if key.include?('=')
76
+
77
+ "#{key}=#{value}"
78
+ end
79
+ strings = NativeStringArray.new(entries)
80
+ FFIBindings.lldb_launch_info_set_environment_entries(@ptr, strings.to_ptr, append ? 1 : 0)
81
+ end
82
+
83
+ # @rbs args: Array[String]
84
+ # @rbs append: bool
85
+ # @rbs return: void
86
+ def arguments=(args, append: false)
87
+ raise ArgumentError, 'arguments must be an Array' unless args.is_a?(Array)
88
+
89
+ @arguments = NativeStringArray.new(args)
90
+ FFIBindings.lldb_launch_info_set_arguments(@ptr, @arguments.to_ptr, append ? 1 : 0)
91
+ end
58
92
 
59
- env_strings = env.map { |k, v| "#{k}=#{v}" }
60
- envp = FFI::MemoryPointer.new(:pointer, env_strings.length + 1)
61
- env_strings.each_with_index do |e, i|
62
- envp[i].put_pointer(0, FFI::MemoryPointer.from_string(e))
93
+ # @rbs return: Array[String]
94
+ def arguments
95
+ count = FFIBindings.lldb_launch_info_get_num_arguments(@ptr)
96
+ (0...count).map { |index| FFIBindings.lldb_launch_info_get_argument_at_index(@ptr, index).to_s }
97
+ end
98
+
99
+ # @rbs return: Array[String]
100
+ def environment_entries
101
+ count = FFIBindings.lldb_launch_info_get_num_environment_entries(@ptr)
102
+ (0...count).map do |index|
103
+ FFIBindings.lldb_launch_info_get_environment_entry_at_index(@ptr, index).to_s
63
104
  end
64
- envp[env_strings.length].put_pointer(0, nil)
105
+ end
106
+
107
+ # @rbs return: FileSpec?
108
+ def executable_file
109
+ file_ptr = FFIBindings.lldb_launch_info_get_executable_file(@ptr)
110
+ return nil if file_ptr.nil? || file_ptr.null?
111
+
112
+ FileSpec.from_ptr(file_ptr, context: context)
113
+ end
114
+
115
+ # @rbs file: FileSpec
116
+ # @rbs add_as_first_arg: bool
117
+ # @rbs return: void
118
+ def executable_file=(file, add_as_first_arg: false)
119
+ raise ArgumentError, 'file must be a FileSpec' unless file.is_a?(FileSpec)
120
+
121
+ FFIBindings.lldb_launch_info_set_executable_file(@ptr, file.to_ptr, add_as_first_arg ? 1 : 0)
122
+ end
123
+
124
+ # @rbs return: Listener?
125
+ def listener
126
+ listener_ptr = FFIBindings.lldb_launch_info_get_listener(@ptr)
127
+ return nil if listener_ptr.nil? || listener_ptr.null?
128
+
129
+ Listener.from_ptr(listener_ptr, context: context)
130
+ end
131
+
132
+ # @rbs value: Listener
133
+ # @rbs return: void
134
+ def listener=(value)
135
+ raise ArgumentError, 'listener must be a Listener' unless value.is_a?(Listener)
136
+
137
+ FFIBindings.lldb_launch_info_set_listener(@ptr, value.to_ptr)
138
+ end
139
+
140
+ # @rbs return: String?
141
+ def process_plugin_name
142
+ FFIBindings.lldb_launch_info_get_process_plugin_name(@ptr)
143
+ end
144
+
145
+ # @rbs value: String
146
+ # @rbs return: void
147
+ def process_plugin_name=(value)
148
+ NativeStringArray.validate!(value)
149
+ FFIBindings.lldb_launch_info_set_process_plugin_name(@ptr, value)
150
+ end
151
+
152
+ # @rbs return: String?
153
+ def shell
154
+ FFIBindings.lldb_launch_info_get_shell(@ptr)
155
+ end
156
+
157
+ # @rbs value: String
158
+ # @rbs return: void
159
+ def shell=(value)
160
+ NativeStringArray.validate!(value)
161
+ FFIBindings.lldb_launch_info_set_shell(@ptr, value)
162
+ end
163
+
164
+ # @rbs fd: Integer
165
+ # @rbs return: bool
166
+ def add_close_file_action(fd)
167
+ FFIBindings.lldb_launch_info_add_close_file_action(@ptr, fd) != 0
168
+ end
169
+
170
+ # @rbs fd: Integer
171
+ # @rbs dup_fd: Integer
172
+ # @rbs return: bool
173
+ def add_duplicate_file_action(fd, dup_fd)
174
+ FFIBindings.lldb_launch_info_add_duplicate_file_action(@ptr, fd, dup_fd) != 0
175
+ end
176
+
177
+ # @rbs fd: Integer
178
+ # @rbs path: String
179
+ # @rbs read: bool
180
+ # @rbs write: bool
181
+ # @rbs return: bool
182
+ def add_open_file_action(fd, path, read: false, write: false)
183
+ NativeStringArray.validate!(path)
184
+ FFIBindings.lldb_launch_info_add_open_file_action(@ptr, fd, path, read ? 1 : 0, write ? 1 : 0) != 0
185
+ end
65
186
 
66
- FFIBindings.lldb_launch_info_set_environment_entries(@ptr, envp, append ? 1 : 0)
187
+ # @rbs fd: Integer
188
+ # @rbs read: bool
189
+ # @rbs write: bool
190
+ # @rbs return: bool
191
+ def add_suppress_file_action(fd, read: false, write: false)
192
+ FFIBindings.lldb_launch_info_add_suppress_file_action(@ptr, fd, read ? 1 : 0, write ? 1 : 0) != 0
67
193
  end
68
194
 
69
195
  # @rbs return: Integer
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ class LineEntry
7
+ prepend NativeLifecycle
8
+
9
+ # @rbs target: Target?
10
+ # @rbs context: Context?
11
+ # @rbs return: void
12
+ def initialize(ptr, target: nil, context: nil)
13
+ @target = target
14
+ initialize_native_object(
15
+ ptr,
16
+ release: ->(released) { FFIBindings.lldb_line_entry_destroy(released) },
17
+ context: context || target&.context
18
+ )
19
+ end
20
+
21
+ # @rbs return: bool
22
+ def valid?
23
+ !@ptr.null? && FFIBindings.lldb_line_entry_is_valid(@ptr) != 0
24
+ end
25
+
26
+ # @rbs return: Address?
27
+ def start_address
28
+ return nil unless valid?
29
+
30
+ ptr = FFIBindings.lldb_line_entry_get_start_address(@ptr)
31
+ return nil if ptr.nil? || ptr.null?
32
+
33
+ Address.from_ptr(ptr, target: @target, context: context)
34
+ end
35
+
36
+ # @rbs return: Address?
37
+ def end_address
38
+ return nil unless valid?
39
+
40
+ ptr = FFIBindings.lldb_line_entry_get_end_address(@ptr)
41
+ return nil if ptr.nil? || ptr.null?
42
+
43
+ Address.from_ptr(ptr, target: @target, context: context)
44
+ end
45
+
46
+ # @rbs return: FileSpec?
47
+ def file_spec
48
+ return nil unless valid?
49
+
50
+ ptr = FFIBindings.lldb_line_entry_get_file_spec(@ptr)
51
+ return nil if ptr.nil? || ptr.null?
52
+
53
+ FileSpec.from_ptr(ptr, context: context)
54
+ end
55
+
56
+ # @rbs return: Integer
57
+ def line
58
+ return INVALID_LINE_NUMBER unless valid?
59
+
60
+ FFIBindings.lldb_line_entry_get_line(@ptr)
61
+ end
62
+
63
+ # @rbs return: Integer
64
+ def column
65
+ return 0 unless valid?
66
+
67
+ FFIBindings.lldb_line_entry_get_column(@ptr)
68
+ end
69
+
70
+ # @rbs return: FFI::Pointer
71
+ def to_ptr
72
+ @ptr
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ class Listener
7
+ prepend NativeLifecycle
8
+
9
+ # @rbs name: String?
10
+ # @rbs context: Context?
11
+ # @rbs pointer: FFI::Pointer?
12
+ # @rbs return: void
13
+ def initialize(name = nil, context: nil, pointer: nil)
14
+ ptr = pointer || FFIBindings.lldb_listener_create(name)
15
+ initialize_native_object(
16
+ ptr,
17
+ release: ->(released) { FFIBindings.lldb_listener_destroy(released) },
18
+ context: context
19
+ )
20
+ end
21
+
22
+ # @rbs ptr: FFI::Pointer
23
+ # @rbs context: Context?
24
+ # @rbs return: Listener
25
+ def self.from_ptr(ptr, context: nil)
26
+ new(context: context, pointer: ptr)
27
+ end
28
+
29
+ # @rbs return: bool
30
+ def valid?
31
+ !@ptr.null? && FFIBindings.lldb_listener_is_valid(@ptr) != 0
32
+ end
33
+
34
+ # @rbs broadcaster: Broadcaster
35
+ # @rbs event_mask: Integer
36
+ # @rbs return: Integer
37
+ def start_listening_for_events(broadcaster, event_mask)
38
+ ensure_open!
39
+ FFIBindings.lldb_listener_start_listening_for_events(@ptr, broadcaster.to_ptr, event_mask)
40
+ end
41
+
42
+ # @rbs broadcaster: Broadcaster
43
+ # @rbs event_mask: Integer
44
+ # @rbs return: bool
45
+ def stop_listening_for_events(broadcaster, event_mask)
46
+ ensure_open!
47
+ FFIBindings.lldb_listener_stop_listening_for_events(@ptr, broadcaster.to_ptr, event_mask) != 0
48
+ end
49
+
50
+ # @rbs timeout_seconds: Integer
51
+ # @rbs return: Event?
52
+ def wait_for_event(timeout_seconds: 0)
53
+ ensure_open!
54
+ timeout = normalize_timeout(timeout_seconds)
55
+ event_ptr = FFIBindings.lldb_listener_wait_for_event(@ptr, timeout)
56
+ event_from_ptr(event_ptr)
57
+ end
58
+
59
+ # @rbs return: Event?
60
+ def peek_event
61
+ ensure_open!
62
+ event_from_ptr(FFIBindings.lldb_listener_peek_event(@ptr))
63
+ end
64
+
65
+ # @rbs return: Event?
66
+ def next_event
67
+ ensure_open!
68
+ event_from_ptr(FFIBindings.lldb_listener_next_event(@ptr))
69
+ end
70
+
71
+ # @rbs return: FFI::Pointer
72
+ def to_ptr
73
+ @ptr
74
+ end
75
+
76
+ private
77
+
78
+ # @rbs timeout: Integer
79
+ # @rbs return: Integer
80
+ def normalize_timeout(timeout)
81
+ unless timeout.is_a?(Integer) && timeout >= 0 && timeout <= 0xFFFFFFFF
82
+ raise ArgumentError, 'timeout_seconds must be an Integer between 0 and UINT32_MAX'
83
+ end
84
+
85
+ timeout
86
+ end
87
+
88
+ # @rbs ptr: FFI::Pointer
89
+ # @rbs return: Event?
90
+ def event_from_ptr(ptr)
91
+ return nil if ptr.nil? || ptr.null?
92
+
93
+ Event.from_ptr(ptr, context: context)
94
+ end
95
+ end
96
+ end
@@ -5,11 +5,16 @@
5
5
  module LLDB
6
6
  # Represents information about a memory region in the target process.
7
7
  class MemoryRegionInfo
8
+ prepend NativeLifecycle
9
+
8
10
  # @rbs ptr: FFI::Pointer
9
11
  # @rbs return: void
10
- def initialize(ptr)
11
- @ptr = ptr # : FFI::Pointer
12
- ObjectSpace.define_finalizer(self, self.class.release(@ptr))
12
+ def initialize(ptr, context: nil)
13
+ initialize_native_object(
14
+ ptr,
15
+ release: ->(released) { FFIBindings.lldb_memory_region_info_destroy(released) },
16
+ context: context
17
+ )
13
18
  end
14
19
 
15
20
  # @rbs ptr: FFI::Pointer
data/lib/lldb/module.rb CHANGED
@@ -4,16 +4,21 @@
4
4
 
5
5
  module LLDB
6
6
  class Module
7
+ prepend NativeLifecycle
8
+
7
9
  # @rbs return: Target?
8
10
  attr_reader :target
9
11
 
10
12
  # @rbs ptr: FFI::Pointer
11
13
  # @rbs target: Target?
12
14
  # @rbs return: void
13
- def initialize(ptr, target:)
14
- @ptr = ptr # : FFI::Pointer
15
+ def initialize(ptr, target:, context: target&.context)
15
16
  @target = target
16
- ObjectSpace.define_finalizer(self, self.class.release(@ptr))
17
+ initialize_native_object(
18
+ ptr,
19
+ release: ->(released) { FFIBindings.lldb_module_destroy(released) },
20
+ context: context
21
+ )
17
22
  end
18
23
 
19
24
  # @rbs ptr: FFI::Pointer
@@ -31,14 +36,34 @@ module LLDB
31
36
  def file_path
32
37
  return nil unless valid?
33
38
 
34
- FFIBindings.lldb_module_get_file_path(@ptr)
39
+ file&.path
35
40
  end
36
41
 
37
42
  # @rbs return: String?
38
43
  def platform_file_path
39
44
  return nil unless valid?
40
45
 
41
- FFIBindings.lldb_module_get_platform_file_path(@ptr)
46
+ platform_file&.path
47
+ end
48
+
49
+ # @rbs return: FileSpec?
50
+ def file
51
+ return nil unless valid?
52
+
53
+ file_ptr = FFIBindings.lldb_module_get_file(@ptr)
54
+ return nil if file_ptr.nil? || file_ptr.null?
55
+
56
+ FileSpec.from_ptr(file_ptr, context: context)
57
+ end
58
+
59
+ # @rbs return: FileSpec?
60
+ def platform_file
61
+ return nil unless valid?
62
+
63
+ file_ptr = FFIBindings.lldb_module_get_platform_file(@ptr)
64
+ return nil if file_ptr.nil? || file_ptr.null?
65
+
66
+ FileSpec.from_ptr(file_ptr, context: context)
42
67
  end
43
68
 
44
69
  # @rbs return: Integer
@@ -48,6 +73,22 @@ module LLDB
48
73
  FFIBindings.lldb_module_get_num_symbols(@ptr)
49
74
  end
50
75
 
76
+ # @rbs index: Integer
77
+ # @rbs return: Symbol?
78
+ def symbol_at_index(index)
79
+ raise InvalidObjectError, 'Module is not valid' unless valid?
80
+
81
+ symbol_ptr = FFIBindings.lldb_module_get_symbol_at_index(@ptr, index)
82
+ return nil if symbol_ptr.nil? || symbol_ptr.null?
83
+
84
+ Symbol.new(symbol_ptr, target: @target, context: context)
85
+ end
86
+
87
+ # @rbs return: Array[Symbol]
88
+ def symbols
89
+ (0...num_symbols).map { |index| symbol_at_index(index) }.compact
90
+ end
91
+
51
92
  # @rbs return: String
52
93
  def to_s
53
94
  file_path || '(unknown module)'
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ module Native
7
+ # @rbs status: Integer
8
+ # @rbs operation: String
9
+ # @rbs error: Error?
10
+ # @rbs return: bool
11
+ def self.check_status!(status, operation, error = nil)
12
+ return true if status == NativeStatus::OK
13
+
14
+ if status == NativeStatus::INTERNAL_ERROR
15
+ message = FFIBindings.lldb_wrapper_last_error_message || 'unknown native exception'
16
+ code = FFIBindings.lldb_wrapper_last_error_code
17
+ raise InternalBindingError.new(operation, message, code)
18
+ end
19
+
20
+ native_error = error || Error.new
21
+ unless native_error.fail?
22
+ native_error.set_error("native operation failed with status #{status}")
23
+ end
24
+ raise OperationError.new(operation, native_error)
25
+ end
26
+
27
+ # @rbs operation: String
28
+ # @rbs error: Error
29
+ # @rbs return: true
30
+ def self.check_error!(operation, error)
31
+ raise OperationError.new(operation, error) if error.fail?
32
+
33
+ true
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ module NativeBuffer
7
+ # @rbs max_size: Integer?
8
+ # @rbs &reader: (FFI::Pointer?, Integer) -> Integer
9
+ # @rbs return: String
10
+ def self.read_c_string(max_size: nil, &reader)
11
+ raise ArgumentError, 'max_size must be non-negative' if max_size&.negative?
12
+ return '' if max_size == 0
13
+
14
+ required = reader.call(nil, 0)
15
+ capacity = if required.positive?
16
+ max_size ? [required + 1, max_size].min : required + 1
17
+ else
18
+ max_size || 256
19
+ end
20
+ capacity = 1 if capacity.zero?
21
+ loop do
22
+ buffer = FFI::MemoryPointer.new(:uint8, capacity)
23
+ written = reader.call(buffer, capacity)
24
+ bytes = buffer.get_bytes(0, [written, capacity].min).delete_suffix("\0")
25
+ return bytes if max_size && (capacity >= max_size || written < capacity - 1)
26
+ return bytes if written < capacity - 1
27
+
28
+ capacity = [capacity * 2, written + 2].max
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ class NativeHandle
7
+ NULL_POINTER = FFI::Pointer.new(0)
8
+
9
+ # @rbs ptr: FFI::Pointer
10
+ # @rbs release: ^(FFI::Pointer) -> void
11
+ # @rbs return: void
12
+ def initialize(ptr, release:)
13
+ # The resource is detached with Array#shift, which is a single Ruby VM
14
+ # operation. Finalizers cannot safely acquire a Mutex on all supported
15
+ # Rubies, so the detach itself must not depend on one.
16
+ @state = [[ptr, release]]
17
+ ObjectSpace.define_finalizer(self, self.class.finalizer(@state))
18
+ end
19
+
20
+ # @rbs return: FFI::Pointer
21
+ def to_ptr
22
+ resource = @state.first
23
+ resource ? resource.first : NULL_POINTER
24
+ end
25
+
26
+ # @rbs return: bool
27
+ def closed?
28
+ @state.empty?
29
+ end
30
+
31
+ # @rbs return: bool
32
+ def close
33
+ resource = @state.shift
34
+ return false unless resource
35
+
36
+ pointer, release = resource
37
+
38
+ release.call(pointer) unless pointer.null?
39
+ true
40
+ end
41
+
42
+ # @rbs state: Array[Array[untyped]]
43
+ # @rbs return: ^(Integer) -> void
44
+ def self.finalizer(state)
45
+ ->(_object_id) do
46
+ resource = state.shift
47
+ next unless resource
48
+
49
+ pointer, release = resource
50
+ next unless pointer && !pointer.null?
51
+
52
+ begin
53
+ release.call(pointer)
54
+ rescue StandardError
55
+ # Finalizers must never raise into an unrelated Ruby thread.
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ module NativeLifecycle
7
+ # @rbs return: bool
8
+ def closed?
9
+ @native_handle ? @native_handle.closed? : false
10
+ end
11
+
12
+ # @rbs return: bool
13
+ def close
14
+ return false unless @native_handle
15
+ return false if @native_handle.closed?
16
+
17
+ send(:close_context) if respond_to?(:close_context, true)
18
+ @native_handle.close
19
+ @ptr = NativeHandle::NULL_POINTER
20
+ true
21
+ end
22
+
23
+ # @rbs return: FFI::Pointer
24
+ def to_ptr
25
+ return NativeHandle::NULL_POINTER if closed?
26
+
27
+ super
28
+ end
29
+
30
+ # @rbs return: void
31
+ def ensure_open!
32
+ raise ClosedObjectError, "#{self.class} is closed" if closed?
33
+ @context&.ensure_open!
34
+ end
35
+
36
+ # @rbs ptr: FFI::Pointer
37
+ # @rbs release: ^(FFI::Pointer) -> void
38
+ # @rbs context: Context?
39
+ # @rbs return: void
40
+ def initialize_native_object(ptr, release:, context: nil)
41
+ @ptr = ptr
42
+ @context = context
43
+ @native_handle = NativeHandle.new(ptr, release: release)
44
+ context&.register(@native_handle)
45
+ end
46
+
47
+ # @rbs return: Context?
48
+ def context
49
+ @context
50
+ end
51
+
52
+ # @rbs return: Context
53
+ def context!
54
+ context || raise(LifecycleError, 'LLDB object has no context')
55
+ end
56
+
57
+ # @rbs return: bool
58
+ def valid?
59
+ return false if closed? || @context&.closed?
60
+
61
+ super
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module LLDB
6
+ class NativeStringArray
7
+ # @rbs strings: Array[String]
8
+ # @rbs return: void
9
+ def initialize(strings)
10
+ @strings = strings.map { |string| NativeStringArray.validate!(string) }
11
+ @string_pointers = @strings.map { |string| FFI::MemoryPointer.from_string(string) }
12
+ @pointer_array = FFI::MemoryPointer.new(:pointer, @string_pointers.length + 1)
13
+
14
+ @string_pointers.each_with_index do |pointer, index|
15
+ @pointer_array[index].put_pointer(0, pointer)
16
+ end
17
+ @pointer_array[@string_pointers.length].put_pointer(0, nil)
18
+ end
19
+
20
+ # @rbs value: String
21
+ # @rbs return: String
22
+ def self.validate!(value)
23
+ raise ArgumentError, 'native string values must be String' unless value.is_a?(String)
24
+ raise ArgumentError, 'native strings must not contain NUL bytes' if value.include?("\0")
25
+
26
+ value
27
+ end
28
+
29
+ # @rbs return: FFI::Pointer
30
+ def to_ptr
31
+ @pointer_array
32
+ end
33
+ end
34
+ end