sass-embedded 1.62.0 → 1.63.1

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.
data/lib/sass/elf.rb ADDED
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {ELF} class.
5
+ #
6
+ # It parses ELF header to extract interpreter.
7
+ # @see https://github.com/torvalds/linux/blob/HEAD/include/uapi/linux/elf.h
8
+ # @see https://github.com/torvalds/linux/blob/HEAD/kernel/kexec_elf.c
9
+ class ELF
10
+ # rubocop:disable Naming/ConstantName
11
+
12
+ # 32-bit ELF base types.
13
+ Elf32_Addr = :__u32
14
+ Elf32_Half = :__u16
15
+ Elf32_Off = :__u32
16
+ Elf32_Sword = :__s32
17
+ Elf32_Word = :__u32
18
+
19
+ # 64-bit ELF base types.
20
+ Elf64_Addr = :__u64
21
+ Elf64_Half = :__u16
22
+ Elf64_SHalf = :__s16
23
+ Elf64_Off = :__u64
24
+ Elf64_Sword = :__s32
25
+ Elf64_Word = :__u32
26
+ Elf64_Xword = :__u64
27
+ Elf64_Sxword = :__s64
28
+
29
+ # rubocop:enable Naming/ConstantName
30
+
31
+ # These constants are for the segment types stored in the image headers
32
+ PT_NULL = 0
33
+ PT_LOAD = 1
34
+ PT_DYNAMIC = 2
35
+ PT_INTERP = 3
36
+ PT_NOTE = 4
37
+ PT_SHLIB = 5
38
+ PT_PHDR = 6
39
+ PT_TLS = 7
40
+ PT_LOOS = 0x60000000
41
+ PT_HIOS = 0x6fffffff
42
+ PT_LOPROC = 0x70000000
43
+ PT_HIPROC = 0x7fffffff
44
+ PT_GNU_EH_FRAME = (PT_LOOS + 0x474e550)
45
+ PT_GNU_STACK = (PT_LOOS + 0x474e551)
46
+ PT_GNU_RELRO = (PT_LOOS + 0x474e552)
47
+ PT_GNU_PROPERTY = (PT_LOOS + 0x474e553)
48
+
49
+ # These constants define the different elf file types
50
+ ET_NONE = 0
51
+ ET_REL = 1
52
+ ET_EXEC = 2
53
+ ET_DYN = 3
54
+ ET_CORE = 4
55
+ ET_LOPROC = 0xff00
56
+ ET_HIPROC = 0xffff
57
+
58
+ EI_NIDENT = 16
59
+
60
+ # rubocop:disable Naming/ConstantName
61
+
62
+ Elf32_Ehdr = [
63
+ [:unsigned_char, :e_ident, EI_NIDENT],
64
+ [Elf32_Half, :e_type],
65
+ [Elf32_Half, :e_machine],
66
+ [Elf32_Word, :e_version],
67
+ [Elf32_Addr, :e_entry],
68
+ [Elf32_Off, :e_phoff],
69
+ [Elf32_Off, :e_shoff],
70
+ [Elf32_Word, :e_flags],
71
+ [Elf32_Half, :e_ehsize],
72
+ [Elf32_Half, :e_phentsize],
73
+ [Elf32_Half, :e_phnum],
74
+ [Elf32_Half, :e_shentsize],
75
+ [Elf32_Half, :e_shnum],
76
+ [Elf32_Half, :e_shstrndx]
77
+ ].freeze
78
+
79
+ Elf64_Ehdr = [
80
+ [:unsigned_char, :e_ident, EI_NIDENT],
81
+ [Elf64_Half, :e_type],
82
+ [Elf64_Half, :e_machine],
83
+ [Elf64_Word, :e_version],
84
+ [Elf64_Addr, :e_entry],
85
+ [Elf64_Off, :e_phoff],
86
+ [Elf64_Off, :e_shoff],
87
+ [Elf64_Word, :e_flags],
88
+ [Elf64_Half, :e_ehsize],
89
+ [Elf64_Half, :e_phentsize],
90
+ [Elf64_Half, :e_phnum],
91
+ [Elf64_Half, :e_shentsize],
92
+ [Elf64_Half, :e_shnum],
93
+ [Elf64_Half, :e_shstrndx]
94
+ ].freeze
95
+
96
+ Elf32_Phdr = [
97
+ [Elf32_Word, :p_type],
98
+ [Elf32_Off, :p_offset],
99
+ [Elf32_Addr, :p_vaddr],
100
+ [Elf32_Addr, :p_paddr],
101
+ [Elf32_Word, :p_filesz],
102
+ [Elf32_Word, :p_memsz],
103
+ [Elf32_Word, :p_flags],
104
+ [Elf32_Word, :p_align]
105
+ ].freeze
106
+
107
+ Elf64_Phdr = [
108
+ [Elf64_Word, :p_type],
109
+ [Elf64_Word, :p_flags],
110
+ [Elf64_Off, :p_offset],
111
+ [Elf64_Addr, :p_vaddr],
112
+ [Elf64_Addr, :p_paddr],
113
+ [Elf64_Xword, :p_filesz],
114
+ [Elf64_Xword, :p_memsz],
115
+ [Elf64_Xword, :p_align]
116
+ ].freeze
117
+
118
+ # rubocop:enable Naming/ConstantName
119
+
120
+ # e_ident[] indexes
121
+ EI_MAG0 = 0
122
+ EI_MAG1 = 1
123
+ EI_MAG2 = 2
124
+ EI_MAG3 = 3
125
+ EI_CLASS = 4
126
+ EI_DATA = 5
127
+ EI_VERSION = 6
128
+ EI_OSABI = 7
129
+ EI_PAD = 8
130
+
131
+ # EI_MAG
132
+ ELFMAG0 = 0x7f
133
+ ELFMAG1 = 'E'.ord
134
+ ELFMAG2 = 'L'.ord
135
+ ELFMAG3 = 'F'.ord
136
+ ELFMAG = [ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3].pack('C*')
137
+ SELFMAG = 4
138
+
139
+ # e_ident[EI_CLASS]
140
+ ELFCLASSNONE = 0
141
+ ELFCLASS32 = 1
142
+ ELFCLASS64 = 2
143
+ ELFCLASSNUM = 3
144
+
145
+ # e_ident[EI_DATA]
146
+ ELFDATANONE = 0
147
+ ELFDATA2LSB = 1
148
+ ELFDATA2MSB = 2
149
+
150
+ def initialize(buffer)
151
+ @buffer = buffer
152
+ @ehdr = read_ehdr
153
+ @buffer.seek(@ehdr[:e_phoff], :SET)
154
+ @proghdrs = Array.new(@ehdr[:e_phnum]) do
155
+ read_phdr
156
+ end
157
+ end
158
+
159
+ def relocatable?
160
+ @ehdr[:e_type] == ET_REL
161
+ end
162
+
163
+ def executable?
164
+ @ehdr[:e_type] == ET_EXEC
165
+ end
166
+
167
+ def shared_object?
168
+ @ehdr[:e_type] == ET_DYN
169
+ end
170
+
171
+ def core?
172
+ @ehdr[:e_type] == ET_CORE
173
+ end
174
+
175
+ def interpreter
176
+ phdr = @proghdrs.find { |p| p[:p_type] == PT_INTERP }
177
+ return if phdr.nil?
178
+
179
+ @buffer.seek(phdr[:p_offset], :SET)
180
+ interpreter = @buffer.read(phdr[:p_filesz])
181
+ raise ArgumentError unless interpreter.end_with?("\0")
182
+
183
+ interpreter.chomp!("\0")
184
+ end
185
+
186
+ private
187
+
188
+ def file_class
189
+ @ehdr[:e_ident][EI_CLASS]
190
+ end
191
+
192
+ def data_encoding
193
+ @ehdr[:e_ident][EI_DATA]
194
+ end
195
+
196
+ def read_ehdr
197
+ @ehdr = { e_ident: @buffer.read(EI_NIDENT).unpack('C*') }
198
+ raise ArgumentError unless @ehdr[:e_ident].slice(EI_MAG0, SELFMAG).pack('C*') == ELFMAG
199
+
200
+ case file_class
201
+ when ELFCLASS32
202
+ Elf32_Ehdr
203
+ when ELFCLASS64
204
+ Elf64_Ehdr
205
+ else
206
+ raise ArgumentError
207
+ end.drop(1).to_h do |field|
208
+ [field[1], read1(field[0])]
209
+ end.merge!(@ehdr)
210
+ end
211
+
212
+ def read_phdr
213
+ case file_class
214
+ when ELFCLASS32
215
+ Elf32_Phdr
216
+ when ELFCLASS64
217
+ Elf64_Phdr
218
+ else
219
+ raise ArgumentError
220
+ end.to_h do |field|
221
+ [field[1], read1(field[0])]
222
+ end
223
+ end
224
+
225
+ def explicit_endian
226
+ case data_encoding
227
+ when ELFDATA2LSB
228
+ '<'
229
+ when ELFDATA2MSB
230
+ '>'
231
+ else
232
+ raise ArgumentError
233
+ end
234
+ end
235
+
236
+ def read1(type)
237
+ case type
238
+ when :__u8
239
+ @buffer.read(1).unpack1('C')
240
+ when :__u16
241
+ @buffer.read(2).unpack1("S#{explicit_endian}")
242
+ when :__u32
243
+ @buffer.read(4).unpack1("L#{explicit_endian}")
244
+ when :__u64
245
+ @buffer.read(8).unpack1("Q#{explicit_endian}")
246
+ when :__s8
247
+ @buffer.read(1).unpack1('c')
248
+ when :__s16
249
+ @buffer.read(2).unpack1("s#{explicit_endian}")
250
+ when :__s32
251
+ @buffer.read(4).unpack1("l#{explicit_endian}")
252
+ when :__s64
253
+ @buffer.read(8).unpack1("q#{explicit_endian}")
254
+ else
255
+ raise ArgumentError
256
+ end
257
+ end
258
+
259
+ INTERPRETER = begin
260
+ proc_self_exe = '/proc/self/exe'
261
+ if File.exist?(proc_self_exe)
262
+ File.open(proc_self_exe, 'rb') do |file|
263
+ elf = ELF.new(file)
264
+ interpreter = elf.interpreter
265
+ if interpreter.nil? && elf.shared_object?
266
+ File.readlink(proc_self_exe)
267
+ else
268
+ interpreter
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ private_constant :ELF
276
+ end
@@ -27,7 +27,7 @@ module Sass
27
27
  @mutex.synchronize do
28
28
  begin
29
29
  id = @dispatcher.subscribe(observer)
30
- rescue EOFError
30
+ rescue Errno::EBUSY
31
31
  @dispatcher = Dispatcher.new
32
32
  id = @dispatcher.subscribe(observer)
33
33
  end
@@ -48,8 +48,8 @@ module Sass
48
48
  @dispatcher.unsubscribe(id)
49
49
  end
50
50
 
51
- def send_message(...)
52
- @dispatcher.send_message(...)
51
+ def send_proto(...)
52
+ @dispatcher.send_proto(...)
53
53
  end
54
54
  end
55
55
 
@@ -6,10 +6,19 @@ module Sass
6
6
  class Embedded
7
7
  # The {Compiler} class.
8
8
  #
9
- # It runs the `dart-sass-embedded` process.
9
+ # It runs the `sass --embedded` process.
10
10
  class Compiler
11
11
  def initialize
12
- @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(*COMMAND, chdir: __dir__)
12
+ @stdin, @stdout, @stderr, @wait_thread = begin
13
+ Open3.popen3(*CLI::COMMAND, '--embedded', chdir: __dir__)
14
+ rescue Errno::ENOENT
15
+ require_relative '../elf'
16
+
17
+ raise if ELF::INTERPRETER.nil?
18
+
19
+ Open3.popen3(ELF::INTERPRETER, *CLI::COMMAND, '--embedded', chdir: __dir__)
20
+ end
21
+
13
22
  @stdin.binmode
14
23
  @stdout.binmode
15
24
  @stdin_mutex = Mutex.new
@@ -40,17 +49,20 @@ module Sass
40
49
  end
41
50
  end
42
51
 
43
- def write(payload)
52
+ def write(id, proto)
44
53
  @stdin_mutex.synchronize do
45
- Varint.write(@stdin, payload.length)
46
- @stdin.write(payload)
54
+ Varint.write(@stdin, Varint.length(id) + proto.length)
55
+ Varint.write(@stdin, id)
56
+ @stdin.write(proto)
47
57
  end
48
58
  end
49
59
 
50
60
  def read
51
61
  @stdout_mutex.synchronize do
52
62
  length = Varint.read(@stdout)
53
- @stdout.read(length)
63
+ id = Varint.read(@stdout)
64
+ proto = @stdout.read(length - Varint.length(id))
65
+ return id, proto
54
66
  end
55
67
  end
56
68
  end
@@ -6,23 +6,23 @@ module Sass
6
6
  #
7
7
  # It dispatches messages between mutliple instances of {Host} and a single {Compiler}.
8
8
  class Dispatcher
9
- PROTOCOL_ERROR_ID = 0xffffffff
9
+ UINT_MAX = 0xffffffff
10
10
 
11
11
  def initialize
12
12
  @compiler = Compiler.new
13
13
  @observers = {}
14
- @id = 0
14
+ @id = 1
15
15
  @mutex = Mutex.new
16
16
 
17
17
  Thread.new do
18
18
  loop do
19
- receive_message
20
- rescue IOError, Errno::EBADF => e
19
+ receive_proto
20
+ rescue IOError, Errno::EBADF, Errno::EPROTO => e
21
21
  @mutex.synchronize do
22
- @id = PROTOCOL_ERROR_ID
22
+ @id = UINT_MAX
23
23
  @observers.values
24
24
  end.each do |observer|
25
- observer.error e
25
+ observer.error(e)
26
26
  end
27
27
  break
28
28
  end
@@ -31,7 +31,7 @@ module Sass
31
31
 
32
32
  def subscribe(observer)
33
33
  @mutex.synchronize do
34
- raise EOFError if @id == PROTOCOL_ERROR_ID
34
+ raise Errno::EBUSY if @id == UINT_MAX
35
35
 
36
36
  id = @id
37
37
  @id = id.next
@@ -46,10 +46,10 @@ module Sass
46
46
 
47
47
  return unless @observers.empty?
48
48
 
49
- if @id == PROTOCOL_ERROR_ID
49
+ if @id == UINT_MAX
50
50
  close
51
51
  else
52
- @id = 0
52
+ @id = 1
53
53
  end
54
54
  end
55
55
  end
@@ -62,33 +62,29 @@ module Sass
62
62
  @compiler.closed?
63
63
  end
64
64
 
65
- def send_message(...)
66
- inbound_message = EmbeddedProtocol::InboundMessage.new(...)
67
- @compiler.write(inbound_message.to_proto)
65
+ def send_proto(...)
66
+ @compiler.write(...)
68
67
  end
69
68
 
70
69
  private
71
70
 
72
- def receive_message
73
- outbound_message = EmbeddedProtocol::OutboundMessage.decode(@compiler.read)
74
- oneof = outbound_message.message
75
- message = outbound_message.public_send(oneof)
76
- case oneof
77
- when :error
78
- @mutex.synchronize do
79
- @id = PROTOCOL_ERROR_ID
80
- message.id == PROTOCOL_ERROR_ID ? @observers.values : [@observers[message.id]]
81
- end.each do |observer|
82
- observer.public_send(oneof, message)
83
- end
84
- when :compile_response, :version_response
71
+ def receive_proto
72
+ id, proto = @compiler.read
73
+ case id
74
+ when 1...UINT_MAX
75
+ @mutex.synchronize { @observers[id] }.receive_proto(proto)
76
+ when 0
77
+ outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
78
+ oneof = outbound_message.message
79
+ message = outbound_message.public_send(oneof)
85
80
  @mutex.synchronize { @observers[message.id] }.public_send(oneof, message)
86
- when :log_event, :canonicalize_request, :import_request, :file_import_request, :function_call_request
87
- Thread.new(@mutex.synchronize { @observers[message.compilation_id] }) do |observer|
88
- observer.public_send(oneof, message)
89
- end
81
+ when UINT_MAX
82
+ outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
83
+ oneof = outbound_message.message
84
+ message = outbound_message.public_send(oneof)
85
+ raise Errno::EPROTO, message.message
90
86
  else
91
- raise ArgumentError, "Unknown OutboundMessage.message #{message}"
87
+ raise Errno::EPROTO
92
88
  end
93
89
  end
94
90
  end
@@ -7,41 +7,44 @@ module Sass
7
7
  #
8
8
  # It stores logger and handles log events.
9
9
  class LoggerRegistry
10
- attr_reader :logger
11
-
12
10
  def initialize(logger)
13
- @logger = Structifier.to_struct(logger, :debug, :warn)
14
- end
11
+ logger = Structifier.to_struct(logger, :debug, :warn)
15
12
 
16
- def log(event)
17
- case event.type
18
- when :DEBUG
19
- if logger.respond_to? :debug
13
+ if logger.respond_to?(:debug)
14
+ define_singleton_method(:debug) do |event|
20
15
  logger.debug(event.message,
21
16
  span: Protofier.from_proto_source_span(event.span))
22
- else
23
- warn(event.formatted)
24
17
  end
25
- when :DEPRECATION_WARNING
26
- if logger.respond_to? :warn
27
- logger.warn(event.message,
28
- deprecation: true,
29
- span: Protofier.from_proto_source_span(event.span),
30
- stack: event.stack_trace)
31
- else
32
- warn(event.formatted)
33
- end
34
- when :WARNING
35
- if logger.respond_to? :warn
18
+ end
19
+
20
+ if logger.respond_to?(:warn) # rubocop:disable Style/GuardClause
21
+ define_singleton_method(:warn) do |event|
36
22
  logger.warn(event.message,
37
- deprecation: false,
23
+ deprecation: event.type == :DEPRECATION_WARNING,
38
24
  span: Protofier.from_proto_source_span(event.span),
39
25
  stack: event.stack_trace)
40
- else
41
- warn(event.formatted)
42
26
  end
43
27
  end
44
28
  end
29
+
30
+ def log(event)
31
+ case event.type
32
+ when :DEBUG
33
+ debug(event)
34
+ when :DEPRECATION_WARNING, :WARNING
35
+ warn(event)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def debug(event)
42
+ Kernel.warn(event.formatted)
43
+ end
44
+
45
+ def warn(event)
46
+ Kernel.warn(event.formatted)
47
+ end
45
48
  end
46
49
 
47
50
  private_constant :LoggerRegistry
@@ -40,7 +40,6 @@ module Sass
40
40
  @logger_registry = LoggerRegistry.new(logger)
41
41
 
42
42
  send_message(compile_request: EmbeddedProtocol::InboundMessage::CompileRequest.new(
43
- id: id,
44
43
  string: unless source.nil?
45
44
  EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
46
45
  source: source,
@@ -67,8 +66,8 @@ module Sass
67
66
  end
68
67
 
69
68
  def version_request
70
- version_response = await do
71
- send_message(version_request: EmbeddedProtocol::InboundMessage::VersionRequest.new(
69
+ version_response = await0 do
70
+ send_message0(version_request: EmbeddedProtocol::InboundMessage::VersionRequest.new(
72
71
  id: id
73
72
  ))
74
73
  end
@@ -76,16 +75,23 @@ module Sass
76
75
  "sass-embedded\t#{version_response.implementation_version}"
77
76
  end
78
77
 
79
- def log_event(message)
80
- @logger_registry.log(message)
81
- end
82
-
83
78
  def compile_response(message)
84
- resolve(message)
79
+ @result = message
80
+ @queue.close
85
81
  end
86
82
 
87
83
  def version_response(message)
88
- resolve(message)
84
+ @result = message
85
+ @queue.close
86
+ end
87
+
88
+ def error(message)
89
+ @error = message
90
+ @queue.close
91
+ end
92
+
93
+ def log_event(message)
94
+ @logger_registry.log(message)
89
95
  end
90
96
 
91
97
  def canonicalize_request(message)
@@ -104,35 +110,59 @@ module Sass
104
110
  send_message(function_call_response: @function_registry.function_call(message))
105
111
  end
106
112
 
107
- def error(message)
108
- reject(CompileError.new(message.message, nil, nil, nil))
113
+ def receive_proto(proto)
114
+ @queue.push(proto)
109
115
  end
110
116
 
111
117
  private
112
118
 
113
- def await
119
+ def await0
114
120
  @connection = @channel.connect(self)
115
- @async = Async.new
121
+ @queue = Queue.new
122
+
116
123
  yield
117
- @async.await
124
+
125
+ @queue.pop
126
+
127
+ raise @error if @error
128
+
129
+ @result
118
130
  ensure
119
131
  @connection&.disconnect
120
132
  end
121
133
 
122
- def resolve(value)
123
- @async.resolve(value)
124
- end
134
+ def await
135
+ @connection = @channel.connect(self)
136
+ @queue = Queue.new
137
+
138
+ yield
125
139
 
126
- def reject(reason)
127
- @async.reject(reason)
140
+ while (proto = @queue.pop)
141
+ outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
142
+ oneof = outbound_message.message
143
+ message = outbound_message.public_send(oneof)
144
+ public_send(oneof, message)
145
+ end
146
+
147
+ raise @error if @error
148
+
149
+ @result
150
+ ensure
151
+ @connection&.disconnect
128
152
  end
129
153
 
130
154
  def id
131
155
  @connection.id
132
156
  end
133
157
 
158
+ def send_message0(...)
159
+ inbound_message = EmbeddedProtocol::InboundMessage.new(...)
160
+ @connection.send_proto(0, inbound_message.to_proto)
161
+ end
162
+
134
163
  def send_message(...)
135
- @connection.send_message(...)
164
+ inbound_message = EmbeddedProtocol::InboundMessage.new(...)
165
+ @connection.send_proto(id, inbound_message.to_proto)
136
166
  end
137
167
  end
138
168
 
@@ -15,15 +15,16 @@ module Sass
15
15
  when :failure
16
16
  raise CompileError.new(
17
17
  result.message,
18
- result.formatted,
19
- result.stack_trace,
20
- from_proto_source_span(result.span)
18
+ result.formatted == '' ? nil : result.formatted,
19
+ result.stack_trace == '' ? nil : result.stack_trace,
20
+ from_proto_source_span(result.span),
21
+ compile_response.loaded_urls
21
22
  )
22
23
  when :success
23
24
  CompileResult.new(
24
25
  result.css,
25
- result.source_map,
26
- result.loaded_urls
26
+ result.source_map == '' ? nil : result.source_map,
27
+ compile_response.loaded_urls
27
28
  )
28
29
  else
29
30
  raise ArgumentError, "Unknown CompileResponse.result #{result}"
@@ -36,8 +37,8 @@ module Sass
36
37
  Logger::SourceSpan.new(from_proto_source_location(source_span.start),
37
38
  from_proto_source_location(source_span.end),
38
39
  source_span.text,
39
- source_span.url,
40
- source_span.context)
40
+ source_span.url == '' ? nil : source_span.url,
41
+ source_span.context == '' ? nil : source_span.context)
41
42
  end
42
43
 
43
44
  def from_proto_source_location(source_location)
@@ -8,6 +8,12 @@ module Sass
8
8
  module Varint
9
9
  module_function
10
10
 
11
+ def length(value)
12
+ return 1 if value < 128
13
+
14
+ (value.bit_length + 6) / 7
15
+ end
16
+
11
17
  def read(readable)
12
18
  value = bits = 0
13
19
  loop do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sass
4
4
  class Embedded
5
- VERSION = '1.62.0'
5
+ VERSION = '1.63.1'
6
6
  end
7
7
  end